|
3548
|
rhythm game but swords by @kosmo1027 | Stardance
|
kosmo1027
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: rhythm game but swords by @kosmo1027 | Star Title: rhythm game but swords by @kosmo1027 | Stardance...
|
1793227905.1387165
|
1793832705.1387165
|
|
57691
|
rhythm pad ! by @kirbyy | Stardance
|
kirbyy
|
0
|
11.0
|
0
|
[]
|
[{"post_id": 73464, "ts": "202 [{"post_id": 73464, "ts": "2026-09-25T04:37:31Z", "hours": 0.6672, "text": "(09/25/26) final devlog for the rhythm pad most likely! Figured out how to work QMK firmware after hours of suffering (though, it won’t track </3) It was such a leaning curve, but joe scotto is the goat. Additonally, readjusted measurments on the plate due to it being mis-aligned. Importing the .dxf file of the PCB was a great help."}, {"post_id": 67069, "ts": "2026-09-13T03:41:14Z", "hours": 2.2136, "text": "(9/12/2026) huge update! finally finished the case after some unfortunate stressing and embarrassing tears (thought I broke fillet tool..). Added the plate and bezel with colors inspired by the Artemis spaceship. Learning CAD for the first time to create this has been such a great experience."}, {"post_id": 65798, "ts": "2026-09-11T05:30:45Z", "hours": 1.9008, "text": "(9/11/2026) not that huge of an update, but I feel like logging the 2 hours I spent overthinking. Starting to work on the plate. It’s gonna be a sandwich mounting style. All the measurements and scale were confusing me so bad, but I think it’s coming along."}, {"post_id": 65176, "ts": "2026-09-10T03:49:10Z", "hours": 1.6953, "text": "(9/9/2026) beginning the construction of the case! I can honestly say I’m super proud of how it’s coming out so far. I did struggle with learning onshape and I still don’t know everything, not by a long shot, but I def feel way more capable. This is just the bottom half."}, {"post_id": 63996, "ts": "2026-09-08T03:05:57Z", "hours": 2.2803, "text": "(9/7/2026) finally finished up the PCB! i definitely spent way too long overthinking the layout and measurements. When routing started to get difficult due to the order of pins in the schematic, I figured I could switch the order. It got wayyy easier, and I didn´t have to cross as much."}, {"post_id": 63246, "ts": "2026-09-06T23:26:32Z", "hours": 2.2453, "text": "(9/6/2026) current iteration of the PCB schematics, just a few days ago, I knew nothing about PCB design but learning has honestly been so fun! I struggled the most with understanding how to route the rotary encoder. I think connecting the switch pins to the matrix works ? May change it if I recieve feedback."}]...
|
Title: rhythm pad ! by @kirbyy | Stardance
Devlog Title: rhythm pad ! by @kirbyy | Stardance
Devlogs:
(9/6/2026) current iteration of the PCB schematics, just a few days ago, I knew nothing about PCB design but learning has honestly been so fun! I struggled the most with understanding how to route the rotary encoder. I think connecting the switch pins to the matrix works ? May change it if I recieve feedback.
(9/7/2026) finally finished up the PCB! i definitely spent way too long overthinking the layout and measurements. When routing started to get difficult due to the order of pins in the schematic, I figured I could switch the order. It got wayyy easier, and I didn´t have to cross as much.
(9/9/2026) beginning the construction of the case! I can honestly say I’m super proud of how it’s coming out so far. I did struggle with learning onshape and I still don’t know everything, not by a long shot, but I def feel way more capable. This is just the bottom half.
(9/11/2026) not that huge of an update, but I feel like logging the 2 hours I spent overthinking. Starting to work on the plate. It’s gonna be a sandwich mounting style. All the measurements and scale were confusing me so bad, but I think it’s coming along.
(9/12/2026) huge update! finally finished the case after some unfortunate stressing and embarrassing tears (thought I broke fillet tool..). Added the plate and bezel with colors inspired by the Artemis spaceship. Learning CAD for the first time to create this has been such a great experience.
(09/25/26) final devlog for the rhythm pad most likely! Figured out how to work QMK firmware after hours of suffering (though, it won’t track </3) It was such a leaning curve, but joe scotto is the goat. Additonally, readjusted measurments on the plate due to it being mis-aligned. Importing the .dxf file of the PCB was a great help....
|
1792716733.87133
|
1793321533.87133
|
|
65827
|
rhythmpad by @Googentity | Stardance
|
Googentity
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: rhythmpad by @Googentity | Stardance
|
1793091173.4053242
|
1793695973.4053242
|
|
40202
|
richy's new site by @richkdev | Stardance
|
richkdev
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: richy's new site by @richkdev | Stardan Title: richy's new site by @richkdev | Stardance...
|
1792687690.9545648
|
1793292490.9545648
|
|
43047
|
rig-wave by @ttyChud | Stardance
|
ttyChud
|
1
|
3.0
|
8
|
[{"ship_num": 1, "ts": "2026-0 [{"ship_num": 1, "ts": "2026-08-12T08:51:29Z", "hours": 3.0, "multiplier": 2.21, "payout": 8, "blessing": "neutral", "text": "For a while it was quiet around here, but Rig Wave is finally live on Vercel — check out the demo at rigwave.vercel.app ( https://rigwave.vercel.app/ )! It’s an ambient wave background where light sweeps across a grid, a glowing trail follows your cursor, and clicks send ripples across the canvas. And if you want it in your own project, you can grab it the shadcn way: npx shadcn@latest add Himanshu121865/rig-wave/rig-wave — it pulls the component straight from the GitHub repo into your code, ready to tweak.", "repo": "https://github.com/Himanshu121865/rig-wave", "demo": "https://rigwave.vercel.app/"}]...
|
[{"post_id": 45647, "ts": "202 [{"post_id": 45647, "ts": "2026-08-12T08:46:24Z", "hours": 0.35, "text": "it’s been quiet sometime but i have now hosted it on vercel for demo rigwave.vercel.app ( https://rigwave.vercel.app/ ) and if someone wanna use it they can also use it through shadcn way (npx shadcn@latest add Himanshu121865/rig-wave/rig-wave) this puts the component straight for the github repo"}, {"post_id": 39459, "ts": "2026-08-02T12:52:12Z", "hours": 3.0864, "text": "so I wanted to build an fragment shader like we see on reactbits but then i got a better idea and here is the final result."}]...
|
Title: rig-wave by @ttyChud | Stardance
Devlogs:
Title: rig-wave by @ttyChud | Stardance
Devlogs:
so I wanted to build an fragment shader like we see on reactbits but then i got a better idea and here is the final result.
it’s been quiet sometime but i have now hosted it on vercel for demo rigwave.vercel.app ( https://rigwave.vercel.app/ ) and if someone wanna use it they can also use it through shadcn way (npx shadcn@latest add Himanshu121865/rig-wave/rig-wave) this puts the component straight for the github repo
Ships:
For a while it was quiet around here, but Rig Wave is finally live on Vercel — check out the demo at rigwave.vercel.app ( https://rigwave.vercel.app/ )! It’s an ambient wave background where light sweeps across a grid, a glowing trail follows your cursor, and clicks send ripples across the canvas. And if you want it in your own project, you can grab it the shadcn way: npx shadcn@latest add Himanshu121865/rig-wave/rig-wave — it pulls the component straight from the GitHub repo into your code, ready to tweak....
|
1792878704.3391025
|
1793742704.3391025
|
|
67686
|
riggermortis by @xenoaitham | Stardance
|
xenoaitham
|
0
|
18.81
|
0
|
[]
|
[{"post_id": 76925, "ts": "202 [{"post_id": 76925, "ts": "2026-10-05T03:12:20Z", "hours": 10.9147, "text": "the sculpt wires into the product flow The proportion and volume sculpting work was measured and gated, but had no real user-facing path yet. That debt got paid: a new command line tool measures a reference image’s proportions and volume ratios into one deterministic file, and both the manual tool and the interactive in-app action now call the same shared function underneath, so they can’t quietly drift apart. The real proof was ordering: sculpt once at rest pose, then animate, and the sculpt has to survive byte-identically. It does, frame-one drift came out effectively zero across seventy checks. Three real bugs earned their keep. First, two test rigs sharing one scene made solved values look wrong, the real cause was one fixture already carrying an earlier sculpt while the other’s baseline measurement accidentally captured the overlapping shape of both figures at once. Took a full debugging ladder to isolate, fixed with a clean scene per case, a lesson the project had already learned once before, now learned twice. Second, baking an animation shifts frame numbers by one internally, so comparing a frame against its own neighbor instead of itself made results look dramatically worse than they actually were. A one-line offset fix, with the reasoning written directly into the check itself this time so it can’t get silently lost again. Third, comparing raw bone positions across an animated sequence turned out to be the wrong check entirely, since a position-based re-solve is supposed to compensate for the current pose’s rotation by design, not match it exactly. Redefined what the check actually verifies, and caught a related bug along the way: an assigned animation silently overrides a rest-pose snapshot unless that animation gets explicitly muted first. Landed with fifteen new tests, the full suite green, and every number comfortably inside its bar except one, a single rig type’s re-sculpt accuracy sits just barely over target, left as the next rough edge."}, {"post_id": 76391, "ts": "2026-10-03T11:46:33Z", "hours": 7.8906, "text": "volume from silhouette The third model decision is made and the volume feature is real. The engine now carries a third pinned model, a segmentation pass, plus a working body sculpt: feed it a reference shape and the character’s hip, waist, chest, and thigh widths move to match it, skeleton provably untouched. Getting there meant benchmarking five real candidate models on real hardware, checksums, speed, and accuracy each, rejecting six other families with the evidence written down. Funniest catch: the model built specifically for segmenting people was completely blind to the test case, zero accuracy, while the general-purpose model saw it fine and ran cheaper too. The mechanism authors real shape keys per body region, so the adjustable sliders are the solved math directly, and every pose/animation path downstream sees a completely unchanged skeleton. The real fight was realizing the first passing test was a lie. A whole-frame comparison technically passed on a completely unsculpted body, since the measurement was too insensitive to tell a correctly sculpted shape from an untouched one. Fixed by scoping comparisons to the actual body regions with no model in the loop, publishing a baseline next to every result so a cheating pass can’t hide again. Smaller lessons banked too: a slider silently defaulting to zero killed half its usable range, one rig type needed its math in a different coordinate space entirely or it silently did nothing, leftover scene state poisoned later measurements, and naive bone-weight assignment broke the hip region specifically. Numbers came out solid: worst-case regional accuracy clears the bar across every test case, width delivery lands exact to four decimal places, solved values track intended targets within about 3% through the real model, and resetting a sculpt restores the original mesh almost exactly. Next: wire both sculpts into the real user-facing flow, and prove the sculpt survives animation, set once, then animate, and the widths hold."}]...
|
Title: riggermortis by @xenoaitham | Stardance
De Title: riggermortis by @xenoaitham | Stardance
Devlogs:
volume from silhouette The third model decision is made and the volume feature is real. The engine now carries a third pinned model, a segmentation pass, plus a working body sculpt: feed it a reference shape and the character’s hip, waist, chest, and thigh widths move to match it, skeleton provably untouched. Getting there meant benchmarking five real candidate models on real hardware, checksums, speed, and accuracy each, rejecting six other families with the evidence written down. Funniest catch: the model built specifically for segmenting people was completely blind to the test case, zero accuracy, while the general-purpose model saw it fine and ran cheaper too. The mechanism authors real shape keys per body region, so the adjustable sliders are the solved math directly, and every pose/animation path downstream sees a completely unchanged skeleton. The real fight was realizing the first passing test was a lie. A whole-frame comparison technically passed on a completely unsculpted body, since the measurement was too insensitive to tell a correctly sculpted shape from an untouched one. Fixed by scoping comparisons to the actual body regions with no model in the loop, publishing a baseline next to every result so a cheating pass can’t hide again. Smaller lessons banked too: a slider silently defaulting to zero killed half its usable range, one rig type needed its math in a different coordinate space entirely or it silently did nothing, leftover scene state poisoned later measurements, and naive bone-weight assignment broke the hip region specifically. Numbers came out solid: worst-case regional accuracy clears the bar across every test case, width delivery lands exact to four decimal places, solved values track intended targets within about 3% through the real model, and resetting a sculpt restores the original mesh almost exactly. Next: wire both sculpts into the real user-facing flow, and prove the sculpt survives animation, set once, then animate, and the widths hold.
the sculpt wires into the product flow The proportion and volume sculpting work was measured and gated, but had no real user-facing path yet. That debt got paid: a new command line tool measures a reference image’s proportions and volume ratios into one deterministic file, and both the manual tool and the interactive in-app action now call the same shared function underneath, so they can’t quietly drift apart. The real proof was ordering: sculpt once at rest pose, then animate, and the sculpt has to survive byte-identically. It does, frame-one drift came out effectively zero across seventy checks. Three real bugs earned their keep. First, two test rigs sharing one scene made solved values look wrong, the real cause was one fixture already carrying an earlier sculpt while the other’s baseline measurement accidentally captured the overlapping shape of both figures at once. Took a full debugging ladder to isolate, fixed with a clean scene per case, a lesson the project had already learned once before, now learned twice. Second, baking an animation shifts frame numbers by one internally, so comparing a frame against its own neighbor instead of itself made results look dramatically worse than they actually were. A one-line offset fix, with the reasoning written directly into the check itself this time so it can’t get silently lost again. Third, comparing raw bone positions across an animated sequence turned out to be the wrong check entirely, since a position-based re-solve is supposed to compensate for the current pose’s rotation by design, not match it exactly. Redefined what the check actually verifies, and caught a related bug along the way: an assigned animation silently overrides a rest-pose snapshot unless that animation gets explicitly muted first. Landed with fifteen new tests, the full suite green, and every number comfortably inside its bar except one, a single rig type’s re-sculpt accuracy sits just barely over target, left as the next rough edge....
|
1792468202.9250567
|
1793332202.9250567
|
|
50885
|
rihans project by @rihan_hsn09 | Stardance
|
rihan_hsn09
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: rihans project by @rihan_hsn09 | Stardance
|
1793364877.0771732
|
1793969677.0771732
|
|
39076
|
rinibot (⌒▽⌒)☆ by @rinimee | Stardance
|
rinimee
|
1
|
1.0
|
0
|
[{"ship_num": 1, "ts": "2026-0 [{"ship_num": 1, "ts": "2026-07-25T18:13:32Z", "hours": 1.0, "multiplier": null, "payout": 0, "blessing": "neutral", "text": "I made my very first coding project by building a SlackBot that’s online 24/7 and got a list of super cool commands you can use! It was really challenging to fix updated code to show up on Slack; I could never figure out where it was coming from until I got help from Hack Club peers! I’m so proud of actually being able to complete a software project and show it to the world! Go ahead and try it out on Slack now with /rinibot! Use /rinibot-help for the full commands list!", "repo": "https://github.com/rinimee/rinibot-code", "demo": "https://hackclub.slack.com/archives/C0BKTDUHVQS/p1785002563021959"}]...
|
[{"post_id": 34657, "ts": "202 [{"post_id": 34657, "ts": "2026-07-25T15:44:17Z", "hours": 0.7153, "text": "Took forever to get this updated to slack, made a bunch of commits on github ;-; but i also made a readme!"}]...
|
Title: rinibot (⌒▽⌒)☆ by @rinimee | Stardance
Dev Title: rinibot (⌒▽⌒)☆ by @rinimee | Stardance
Devlogs:
Took forever to get this updated to slack, made a bunch of commits on github ;-; but i also made a readme!
Ships:
I made my very first coding project by building a SlackBot that’s online 24/7 and got a list of super cool commands you can use! It was really challenging to fix updated code to show up on Slack; I could never figure out where it was coming from until I got help from Hack Club peers! I’m so proud of actually being able to complete a software project and show it to the world! Go ahead and try it out on Slack now with /rinibot! Use /rinibot-help for the full commands list!...
|
1793292704.0321531
|
1793379104.0321531
|
|
62325
|
rinifolio by @rinimee | Stardance
|
rinimee
|
1
|
3.0
|
5
|
[{"ship_num": 1, "ts": "2026-0 [{"ship_num": 1, "ts": "2026-09-22T17:34:32Z", "hours": 3.0, "multiplier": 3.45, "payout": 5, "blessing": "neutral", "text": "I made a portfolio template made for those who would like to log in their projects! It consists of an information panel for those who would like to add their information about wo they are and what they do, and a place to input 9 projects, with a picture, title, and description! Something that was really challenging was getting the CSS right! While it seemed easy, I had to get a lot of help just to fix up my code and make it look correct. However, I am very proud of the result, especially how the ambience of the UI turned out! All the information needed to run my project is included in my GitHub repository. Please check it out, and thank you so much!", "repo": "https://github.com/rinimee/rinifolio", "demo": "https://rinimee.github.io/rinifolio/"}]...
|
[{"post_id": 70678, "ts": "202 [{"post_id": 70678, "ts": "2026-09-19T15:59:45Z", "hours": 1.3519, "text": "doneee with this ;-; I did NOT think css would take an hour. Most of this was because of this one probelm with my CSS. I have a span function in my html with the text “click to add cover” but it ended up not being visible. I ended up leaving that as is after 30 minutes of trying to find the problem. Oh well, at least i’m done now! I don’t want to worry about it, especially since i need those 30 hours complete all the while worrying about if my code is good enough…anyway, here is the final look! I’ll be prepping this and preparing it for shipping in the meantime, so rinimee out"}, {"post_id": 70277, "ts": "2026-09-18T21:05:16Z", "hours": 0.3261, "text": "CSS for first tab donee! Yayy! With a lot of help from copilot (I had too much trouble because everything wasn’t lining up properly ) I was able to finish the CSS for the first tab! There were a lot of mistakes and annoying loopholes i found in my code which isn’t usual, so I had copilot pick out the weeds. I did write the entire code for css!, copilot tweaked it up and made sure it was clean, so, DONT GET THE WRONG IDEA! here’s how it looks like, so rinimee out"}, {"post_id": 69824, "ts": "2026-09-17T21:06:04Z", "hours": 0.4172, "text": "MOAR STUFF DONE!! alright, i got a lot more productive! I did a lot of javascript, which allowed me to save information locally using localStorage and then I also tweaked some tab switching! Also, I officially started my CSS! I added a background which took FOREVER, but then I realized that I ended up using the function url(‘/whimsybg.jpg’) which is wrong! The backslash is only used for actual links! If its saved in your folder you don’t need it! I also added a cool font and styled the buttons! Here’s a quick snappy, rinimee out"}, {"post_id": 69429, "ts": "2026-09-17T01:16:10Z", "hours": 0.4678, "text": "first looks! I lowkey needed a last hour to fulfill the 30 hour mark, and with only 2 weeks left…the pressure is on! The idea for this is to create a portfolio page for your projects! It’s going to look very similar to my rinimee’s journal without all the fancy stuff. I finished working on the html for the most part, and wanted to switch up some stuff, like adding a textarea block (allowing to manipulate a box for text) for the first tab and adding 9 project spots in the second tab! I’ll be working on javascript next, basing it off once more from my previous project. alright gang rinimee out"}]...
|
Title: rinifolio by @rinimee | Stardance
Devlogs: Title: rinifolio by @rinimee | Stardance
Devlogs:
first looks! I lowkey needed a last hour to fulfill the 30 hour mark, and with only 2 weeks left…the pressure is on! The idea for this is to create a portfolio page for your projects! It’s going to look very similar to my rinimee’s journal without all the fancy stuff. I finished working on the html for the most part, and wanted to switch up some stuff, like adding a textarea block (allowing to manipulate a box for text) for the first tab and adding 9 project spots in the second tab! I’ll be working on javascript next, basing it off once more from my previous project. alright gang rinimee out
MOAR STUFF DONE!! alright, i got a lot more productive! I did a lot of javascript, which allowed me to save information locally using localStorage and then I also tweaked some tab switching! Also, I officially started my CSS! I added a background which took FOREVER, but then I realized that I ended up using the function url(‘/whimsybg.jpg’) which is wrong! The backslash is only used for actual links! If its saved in your folder you don’t need it! I also added a cool font and styled the buttons! Here’s a quick snappy, rinimee out
CSS for first tab donee! Yayy! With a lot of help from copilot (I had too much trouble because everything wasn’t lining up properly ) I was able to finish the CSS for the first tab! There were a lot of mistakes and annoying loopholes i found in my code which isn’t usual, so I had copilot pick out the weeds. I did write the entire code for css!, copilot tweaked it up and made sure it was clean, so, DONT GET THE WRONG IDEA! here’s how it looks like, so rinimee out
doneee with this ;-; I did NOT think css would take an hour. Most of this was because of this one probelm with my CSS. I have a span function in my html with the text “click to add cover” but it ended up not being visible. I ended up leaving that as is after 30 minutes of trying to find the problem. Oh well, at least i’m done now! I don’t want to worry about it, especially since i need those 30 hours complete all the while worrying about if my code is good enough…anyway, here is the final look! I’ll be prepping this and preparing it for shipping in the meantime, so rinimee out
Ships:
I made a portfolio template made for those who would like to log in their projects! It consists of an information panel for those who would like to add their information about wo they are and what they do, and a place to input 9 projects, with a picture, title, and description! Something that was really challenging was getting the CSS right! While it seemed easy, I had to get a lot of help just to fix up my code and make it look correct. However, I am very proud of the result, especially how the ambience of the UI turned out! All the information needed to run my project is included in my GitHub repository. Please check it out, and thank you so much!...
|
1793209971.1055057
|
1793296371.1055057
|
|
39074
|
rinimee's journal (⌒▽⌒)☆ by @rinimee | Stardan rinimee's journal (⌒▽⌒)☆ by @rinimee | Stardance...
|
rinimee
|
1
|
6.0
|
0
|
[{"ship_num": 1, "ts": "2026-0 [{"ship_num": 1, "ts": "2026-07-28T21:37:11Z", "hours": 6.0, "multiplier": null, "payout": 0, "blessing": "neutral", "text": "I made a website where you can journal a couple things throughout the month! It includes a space for journal information, a habit tracker, and a “fav monthly songs playlist!” It was challenging to learn the HTML/CSS/Java but with tons of help I finally got myself to complete over 700 lines of code over the course of 3-4 days! I’m so proud of being able to just complete all of this! For those trying out my project, read my READme on GitHub before trying the website to catch any missed info. Thank you! ⸜(。˃ ᵕ ˂ )⸝♡", "repo": "https://github.com/rinimee/rinimeesjournalcode", "demo": "https://rinimee.github.io/rinimeesjournalcode/"}]...
|
[{"post_id": 36516, "ts": "202 [{"post_id": 36516, "ts": "2026-07-28T17:55:11Z", "hours": 0.3733, "text": "FINALLY COMPLETE!!! oh my goodness, its complete! after 670 lines of code (not kidding ;-;) and a little over 6 hours of coding, its done! took a while, but i got there! now, ive gotta do some github work, look at the requirements, and then im all good for submission!"}, {"post_id": 36433, "ts": "2026-07-28T15:38:19Z", "hours": 1.0244, "text": "lookin good! so now im gonna do some extra edits of myself after listening to that habit tracker tutorial because after learning so much, I feel I can edit the tracker a little more! Ill keep yall updated. Also, my github commits are weird…it shows three even though I tried making like five…turns out theyre in a seperate branch altogether. I need some help on that…"}, {"post_id": 36135, "ts": "2026-07-28T00:55:48Z", "hours": 0.2708, "text": "It looks sooo good!! so i just completed the css for the fav songs section and now all i have left is to actually code the spot where you can upload your favorite song’s album cover! i had to rely heavily on outside help like friends +google gemini for this so kudos to them for helping! ill also be editing some extra things on the habit tracker/personal information stuff so that it aligns nicely! ps…i still gotta edit that “july” thingy at the top fo the page sighh"}, {"post_id": 36092, "ts": "2026-07-27T23:26:01Z", "hours": 3.0306, "text": "I FORGOT TO DO MORE DEVLOGS RAHHHH soooo completed the habit tracker, and now im doing tabs setup and other things like journal info…. heres a couple shots! im working on making this super cool favorite songs section where you can upload a picture of whatever song album it is and a place where i can change the name of the song in the box… so excited!"}, {"post_id": 34797, "ts": "2026-07-25T19:51:37Z", "hours": 1.1042, "text": "css done for the tracker, gonna do java in a bit or tmrw :3"}, {"post_id": 34744, "ts": "2026-07-25T18:31:30Z", "hours": 0.4322, "text": "just started! using a tutorial side by side but after that I’ll be on my own. here’s a quick snap of whats to come, im so excited to learn html and code!"}]...
|
Title: rinimee's journal (⌒▽⌒)☆ by @rinimee | Title: rinimee's journal (⌒▽⌒)☆ by @rinimee | Stardance
Devlogs:
just started! using a tutorial side by side but after that I’ll be on my own. here’s a quick snap of whats to come, im so excited to learn html and code!
css done for the tracker, gonna do java in a bit or tmrw :3
I FORGOT TO DO MORE DEVLOGS RAHHHH soooo completed the habit tracker, and now im doing tabs setup and other things like journal info…. heres a couple shots! im working on making this super cool favorite songs section where you can upload a picture of whatever song album it is and a place where i can change the name of the song in the box… so excited!
It looks sooo good!! so i just completed the css for the fav songs section and now all i have left is to actually code the spot where you can upload your favorite song’s album cover! i had to rely heavily on outside help like friends +google gemini for this so kudos to them for helping! ill also be editing some extra things on the habit tracker/personal information stuff so that it aligns nicely! ps…i still gotta edit that “july” thingy at the top fo the page sighh
lookin good! so now im gonna do some extra edits of myself after listening to that habit tracker tutorial because after learning so much, I feel I can edit the tracker a little more! Ill keep yall updated. Also, my github commits are weird…it shows three even though I tried making like five…turns out theyre in a seperate branch altogether. I need some help on that…
FINALLY COMPLETE!!! oh my goodness, its complete! after 670 lines of code (not kidding ;-;) and a little over 6 hours of coding, its done! took a while, but i got there! now, ive gotta do some github work, look at the requirements, and then im all good for submission!
Ships:
I made a website where you can journal a couple things throughout the month! It includes a space for journal information, a habit tracker, and a “fav monthly songs playlist!” It was challenging to learn the HTML/CSS/Java but with tons of help I finally got myself to complete over 700 lines of code over the course of 3-4 days! I’m so proud of being able to just complete all of this! For those trying out my project, read my READme on GitHub before trying the website to catch any missed info. Thank you! ⸜(。˃ ᵕ ˂ )⸝♡...
|
1793278437.2481942
|
1793364837.2481942
|
|
51327
|
rinnieOS by @erin3 | Stardance
|
erin3
|
2
|
17.0
|
0
|
[{"ship_num": 1, "ts": "2026-0 [{"ship_num": 1, "ts": "2026-09-29T20:58:44Z", "hours": 9.0, "multiplier": null, "payout": 0, "blessing": "neutral", "text": "I made a WebOS for the WebOS mission, with a bookmarker app for reading notes, a calculator, settings, a calendar, and a small weather feature. It was so fun to make and I have learnt so much!", "repo": "https://github.com/rinnie3/rinnieOS", "demo": "https://rinnie3.github.io/rinnieOS/"}, {"ship_num": 2, "ts": "2026-08-21T18:26:19Z", "hours": 8.0, "multiplier": null, "payout": 0, "blessing": "neutral", "text": "I made a WebOS for the first time, with 3 apps and some customisable features! I had some difficulty with using CSS for the styles, but overall, I enjoyed making my OS :D.", "repo": "https://github.com/rinnie3/rinnieOS", "demo": "https://rinnie3.github.io/rinnieOS/"}]...
|
[{"post_id": 75360, "ts": "202 [{"post_id": 75360, "ts": "2026-09-29T20:50:29Z", "hours": 3.6728, "text": "I am now finished my OS! I added a calendar app, and on the taskbar, it now shows which apps are open and is a shortcut to access each one (i am not sure how useful this is but I wanted to add a new feature)."}, {"post_id": 74571, "ts": "2026-09-27T17:29:34Z", "hours": 5.0453, "text": "I fixed many problems (which I discovered eventually) on my OS and developed the apps: bookmarker app - I added a search feature, and an edit feature to edit past notes. I also developed the calculator by changing the order of the numbers - i don’t know why I hadn’t already because the numbers were descending from 9 strangely, but… and I also added a percentage and negative/positive number function. In the settings app, I made the window colour editable as an appearance option. I want to add a few more apps, now that I have gotten the hang of creating and developing them but I hope I get it finished before Stardance challenge finishes ><"}, {"post_id": 51789, "ts": "2026-08-21T17:06:32Z", "hours": 3.0625, "text": "I now have three working apps! And a weather/calendar feature. I tried to make it more customisable by adding a settings feature where you can change the background colour, font, and whether or not to have translucent windows for the welcome and calendar windows (as even I wasn’t sure about the translucent ;D). I think I have finished my first OS! I am glad I got to code in 3 different languages as well."}, {"post_id": 50852, "ts": "2026-08-20T09:16:48Z", "hours": 2.0339, "text": "I added my first app! (it doesn’t work yet though…) I also found some problems like when I dragged the window, it kept changing size but I managed to fix them. I also made the background of the window slightly transparent, although I am not completely sure that I like it. :D"}, {"post_id": 50314, "ts": "2026-08-19T14:05:27Z", "hours": 1.0508, "text": "Continuing to improve my OS: I managed to add a door handle and a window header, but I’m not quite sure what the door handle is for… I think the window header makes it look much neater however. (^v^) I also added a close button but for some reason I can’t add an open on the window as well, so I made it come out of the rinnieOS label on the top bar."}, {"post_id": 50249, "ts": "2026-08-19T12:20:46Z", "hours": 1.6039, "text": "Making my first OS: The guide was very helpful so I have been able to develop it. I am glad I was able to work out how to use the dragging feature on my window and get the time to work. I am still working on trying to add a handle and to get it to open and close - I hope I can do it! :D"}]...
|
Title: rinnieOS by @erin3 | Stardance
Devlogs:
Ma Title: rinnieOS by @erin3 | Stardance
Devlogs:
Making my first OS: The guide was very helpful so I have been able to develop it. I am glad I was able to work out how to use the dragging feature on my window and get the time to work. I am still working on trying to add a handle and to get it to open and close - I hope I can do it! :D
Continuing to improve my OS: I managed to add a door handle and a window header, but I’m not quite sure what the door handle is for… I think the window header makes it look much neater however. (^v^) I also added a close button but for some reason I can’t add an open on the window as well, so I made it come out of the rinnieOS label on the top bar.
I added my first app! (it doesn’t work yet though…) I also found some problems like when I dragged the window, it kept changing size but I managed to fix them. I also made the background of the window slightly transparent, although I am not completely sure that I like it. :D
I now have three working apps! And a weather/calendar feature. I tried to make it more customisable by adding a settings feature where you can change the background colour, font, and whether or not to have translucent windows for the welcome and calendar windows (as even I wasn’t sure about the translucent ;D). I think I have finished my first OS! I am glad I got to code in 3 different languages as well.
I fixed many problems (which I discovered eventually) on my OS and developed the apps: bookmarker app - I added a search feature, and an edit feature to edit past notes. I also developed the calculator by changing the order of the numbers - i don’t know why I hadn’t already because the numbers were descending from 9 strangely, but… and I also added a percentage and negative/positive number function. In the settings app, I made the window colour editable as an appearance option. I want to add a few more apps, now that I have gotten the hang of creating and developing them but I hope I get it finished before Stardance challenge finishes ><
I am now finished my OS! I added a calendar app, and on the taskbar, it now shows which apps are open and is a shortcut to access each one (i am not sure how useful this is but I wanted to add a new feature).
Ships:
I made a WebOS for the first time, with 3 apps and some customisable features! I had some difficulty with using CSS for the styles, but overall, I enjoyed making my OS :D.
I made a WebOS for the WebOS mission, with a bookmarker app for reading notes, a calculator, settings, a calendar, and a small weather feature. It was so fun to make and I have learnt so much!...
|
1793264075.9895957
|
1793350475.9895957
|
|
18359
|
rinoOS by @justrivaan | Stardance
|
justrivaan
|
1
|
1.0
|
0
|
[{"ship_num": 1, "ts": "2026-0 [{"ship_num": 1, "ts": "2026-06-26T07:19:07Z", "hours": 1.0, "multiplier": null, "payout": 0, "blessing": "neutral", "text": "the mission said build a personal website. i didn’t want to do the usual thing, a page with my name, a list of projects, some links. that’s boring and nobody actually reads it. i wanted something where you actually have to explore to find out who i am. so instead of a website about me, i made an OS that feels like mine. live like rino. you boot in, pick a wallpaper from 8 presets, then land on a full desktop with a dock, draggable and resizable windows, a top bar with a live clock, and a right click menu on the desktop. there’s a guide app that walks you through how to get around, a sticky notes app you can actually type in, and a jarvis terminal where you type commands like “hey jarvis, open projects” and it actually does it. the windows have all three buttons working, red closes, yellow minimizes the window into a tray tile next to the dock, green fullscreens it. everything is one single html file, no frameworks, no libraries, no backend. what was challenging: a lot of small things that looked easy and weren’t. getting window drag and resize to not fight each other took a while. the guide hint bubble was floating to the wrong spot because a css transform on the dock was silently breaking position fixed for anything inside it, took actual debugging to figure that one out. the boot bar looked smooth in code but felt jittery visually, ended up rewriting it with requestAnimationFrame timed against actual elapsed time instead of random setInterval increments. what i’m proud of: it actually feels like an OS. the boot sequence, the wallpaper picker before you even see the desktop, the tray for minimized windows, the way the guide hint pops up and points at the right icon. none of that was in the original mission brief, i just kept adding things because it didn’t feel done yet. to test it: wait for it to boot, pick any wallpaper, click around the dock. right click the empty desktop for shortcuts. open the terminal and try “hey jarvis, help” to see what it can do. if you want to see the projects window, click the folder icon or say “hey jarvis, open projects.”", "repo": "https://github.com/officiallyrivaan/rinoOS", "demo": "https://officiallyrivaan.github.io/rinoOS/"}]...
|
[{"post_id": 12906, "ts": "202 [{"post_id": 12906, "ts": "2026-06-21T04:09:06Z", "hours": 0.0, "text": "Last thing before this is finished. Added an actual learning guide for the OS, rather than having to figure it out by trial and error. There’s now a Guide app, with a teal icon located at the start of the dock. It launches into a window that takes you through all the basics, like opening applications, resizing and moving windows, what the three buttons for windows do, the context menu, talking to Jarvis in the terminal. When you first land on the desktop, a tooltip will pop up that shows you where the Guide icon is, and says “New here? Click Guide to get to know the OS.” Also, I added the context menu on the desktop. Just do a right-click on any empty space on your desktop and there will be quick access to such things as opening terminal, creating a sticky note, opening guides, changing wallpapers. However, you won’t see this menu when you perform the same action over an opened application, it is only visible on an empty desktop. I had to deal with some bugs that not many people are even going to notice, but which were crucial for my program. After I implemented the resizing functionality, the dragging became somewhat slow, which resulted from a css transition intended only for maximizing an application being applied to dragging and resizing as well. and there was another issue with the hint bubble being pointed to the guide icon. That one was a strange one, it turned out that a transform on the dock’s container element broke the position:fixed styling of everything within it, so the bubble was aligning itself to the center of the dock rather than the screen. Moved it outside of it and it now perfectly aligns with the icon, double checked on different screen sizes. Finally, the boot sequence needed some work too. The animation itself was smooth enough, but at the total duration of 2.6 seconds, it just felt like skipping through rather than loading 5 messages. Slowed it down to about 4 seconds and added a smooth crossfade in between each message instead of the abrupt change, and now it feels like an actual boot sequence, not just random colors. That is all I have for now for rinoOS. Boot up, choose a background, click around, talk to jarvis when bored. Live the rino life 🟣"}, {"post_id": 12450, "ts": "2026-06-20T09:50:01Z", "hours": 0.0, "text": "picking up from devlog #1. last time the three window buttons just sat there doing nothing, red worked but yellow and green were dead weight. fixed that.minimize now actually minimizes. click yellow and the window shrinks down and tucks itself into a little tray right next to the dock, like a real taskbar. click the tile and it pops back open exactly where it was.fullscreen works too. green button blows the window up to fill the whole desktop, click again and it snaps back to wherever it was sitting before. sticky notes is finally here. it’s its own app in the dock now, click the + tile and a new note shows up, type whatever, delete it with the x in the corner. they’re scattered at slight angles so it actually looks like sticky notes and not a grid.also made every window resizable from the bottom right corner, figured if you’re gonna explore my OS you should be able to make a window bigger to actually read stuff instead of squinting. ran into one annoying bug along the way: dragging windows around started feeling laggy and weird after i added resizing, turned out a css transition meant only for the maximize animation was quietly applying to every drag and resize too, so windows were animating toward your cursor instead of just following it. fixed by only turning that transition on for the maximize snap and keeping drag/resize completely instant. one more update coming next."}, {"post_id": 11220, "ts": "2026-06-18T11:24:52Z", "hours": 0.0, "text": "so the mission was basically: build a personal website, but make it actually interesting instead of the usual “hi, here’s my resume” page nobody reads. my first instinct was to do the safe thing, a simple portfolio site, projects listed top to bottom, done in an hour. but that felt like the laziest version of the mission. anyone can make a list of stuff they built.so instead i made rinoOS, (rino being my digital identity) live like rino. the idea: instead of reading about me, you boot into a whole fake OS and explore it like it’s actually mine. you start on a boot screen that fake loads stuff like “syncing f1 telemetry… brewing chai…” then you pick a wallpaper before you even see the desktop. after that you’re dropped into a real desktop with draggable windows, a dock, and a working clock, and you click around to find out who i am instead of scrolling a page.i built most of this outside the repo first, just working on the html locally and testing it as i went, then finally moved everything over once it actually worked.what’s in it so far: 8 wallpapers, pick your vibe before you boot in. windows for About, Projects, Skills, F1 Fan Zone, and Contact. a Jarvis terminal you can actually type commands into, like hey jarvis, open projects.the projects window has my real stuff: Jarvis, a voice assistant i built with whisper and ollama, a live F1 telemetry dashboard, and Time Debt, a roblox game where you’re racing the clock to collect as much as possible before time runs out. biggest issue so far: it looked perfect locally, then i pushed it to github pages and got a blank readme instead of my site. turns out pages only serves a file named exactly index.html. dumb mistake, learned it fast. next up: a sticky notes app and proper minimize to dock animations. live like rino 🟣"}]...
|
Title: rinoOS by @justrivaan | Stardance
Devlogs: Title: rinoOS by @justrivaan | Stardance
Devlogs:
so the mission was basically: build a personal website, but make it actually interesting instead of the usual “hi, here’s my resume” page nobody reads. my first instinct was to do the safe thing, a simple portfolio site, projects listed top to bottom, done in an hour. but that felt like the laziest version of the mission. anyone can make a list of stuff they built.so instead i made rinoOS, (rino being my digital identity) live like rino. the idea: instead of reading about me, you boot into a whole fake OS and explore it like it’s actually mine. you start on a boot screen that fake loads stuff like “syncing f1 telemetry… brewing chai…” then you pick a wallpaper before you even see the desktop. after that you’re dropped into a real desktop with draggable windows, a dock, and a working clock, and you click around to find out who i am instead of scrolling a page.i built most of this outside the repo first, just working on the html locally and testing it as i went, then finally moved everything over once it actually worked.what’s in it so far: 8 wallpapers, pick your vibe before you boot in. windows for About, Projects, Skills, F1 Fan Zone, and Contact. a Jarvis terminal you can actually type commands into, like hey jarvis, open projects.the projects window has my real stuff: Jarvis, a voice assistant i built with whisper and ollama, a live F1 telemetry dashboard, and Time Debt, a roblox game where you’re racing the clock to collect as much as possible before time runs out. biggest issue so far: it looked perfect locally, then i pushed it to github pages and got a blank readme instead of my site. turns out pages only serves a file named exactly index.html. dumb mistake, learned it fast. next up: a sticky notes app and proper minimize to dock animations. live like rino 🟣
picking up from devlog #1. last time the three window buttons just sat there doing nothing, red worked but yellow and green were dead weight. fixed that.minimize now actually minimizes. click yellow and the window shrinks down and tucks itself into a little tray right next to the dock, like a real taskbar. click the tile and it pops back open exactly where it was.fullscreen works too. green button blows the window up to fill the whole desktop, click again and it snaps back to wherever it was sitting before. sticky notes is finally here. it’s its own app in the dock now, click the + tile and a new note shows up, type whatever, delete it with the x in the corner. they’re scattered at slight angles so it actually looks like sticky notes and not a grid.also made every window resizable from the bottom right corner, figured if you’re gonna explore my OS you should be able to make a window bigger to actually read stuff instead of squinting. ran into one annoying bug along the way: dragging windows around started feeling laggy and weird after i added resizing, turned out a css transition meant only for the maximize animation was quietly applying to every drag and resize too, so windows were animating toward your cursor instead of just following it. fixed by only turning that transition on for the maximize snap and keeping drag/resize completely instant. one more update coming next.
Last thing before this is finished. Added an actual learning guide for the OS, rather than having to figure it out by trial and error. There’s now a Guide app, with a teal icon located at the start of the dock. It launches into a window that takes you through all the basics, like opening applications, resizing and moving windows, what the three buttons for windows do, the context menu, talking to Jarvis in the terminal. When you first land on the desktop, a tooltip will pop up that shows you where the Guide icon is, and says “New here? Click Guide to get to know the OS.” Also, I added the context menu on the desktop. Just do a right-click on any empty space on your desktop and there will be quick access to such things as opening terminal, creating a sticky note, opening guides, changing wallpapers. However, you won’t see this menu when you perform the same action over an opened application, it is only visible on an empty desktop. I had to deal with some bugs that not many people are even going to notice, but which were crucial for my program. After I implemented the resizing functionality, the dragging became somewhat slow, which resulted from a css transition intended only for maximizing an application being applied to dragging and resizing as well. and there was another issue with the hint bubble being pointed to the guide icon. That one was a strange one, it turned out that a transform on the dock’s container element broke the position:fixed styling of everything within it, so the bubble was aligning itself to the center of the dock rather than the screen. Moved it outside of it and it now perfectly aligns with the icon, double checked on different screen sizes. Finally, the boot sequence needed some work too. The animation itself was smooth enough, but at the total duration of 2.6 seconds, it just felt like skipping through rather than loading 5 messages. Slowed it down to about 4 seconds and added a smooth crossfade in between each message instead of the abrupt change, and now it feels like an actual boot sequence, not just random colors. That is all I have for now for rinoOS. Boot up, choose a background, click around, talk to jarvis when bored. Live the rino life 🟣
Ships:
the mission said build a personal website. i didn’t want to do the usual thing, a page with my name, a list of projects, some links. that’s boring and nobody actually reads it. i wanted something where you actually have to explore to find out who i am. so instead of a website about me, i made an OS that feels like mine. live like rino. you boot in, pick a wallpaper from 8 presets, then land on a full desktop with a dock, draggable and resizable windows, a top bar with a live clock, and a right click menu on the desktop. there’s a guide app that walks you through how to get around, a sticky notes app you can actually type in, and a jarvis terminal where you type commands like “hey jarvis, open projects” and it actually does it. the windows have all three buttons working, red closes, yellow minimizes the window into a tray tile next to the dock, green fullscreens it. everything is one single html file, no frameworks, no libraries, no backend. what was challenging: a lot of small things that looked easy and weren’t. getting window drag and resize to not fight each other took a while. the guide hint bubble was floating to the wrong spot because a css transform on the dock was silently breaking position fixed for anything inside it, took actual debugging to figure that one out. the boot bar looked smooth in code but felt jittery visually, ended up rewriting it with requestAnimationFrame timed against actual elapsed time instead of random setInterval increments. what i’m proud of: it actually feels like an OS. the boot sequence, the wallpaper picker before you even see the desktop, the tray for minimized windows, the way the guide hint pops up and points at the right icon. none of that was in the original mission brief, i just kept adding things because it didn’t feel done yet. to test it: wait for it to boot, pick any wallpaper, click around the dock. right click the empty desktop for shortcuts. open the terminal and try “hey jarvis, help” to see what it can do. if you want to see the projects window, click the folder icon or say “hey jarvis, open projects.”...
|
1792882038.465076
|
1793746038.465076
|
|
59245
|
rip-off Mario I think by @phu404 | Stardance
|
phu404
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: rip-off Mario I think by @phu404 | Stardanc Title: rip-off Mario I think by @phu404 | Stardance...
|
1792788760.5389678
|
1793393560.5389678
|
|
43788
|
riri web by @pri3 | Stardance
|
pri3
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: riri web by @pri3 | Stardance
|
1793238556.0155528
|
1793843356.0155528
|
|
729
|
risc-v emulator by @Luke | Stardance
|
Luke
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: risc-v emulator by @Luke | Stardance
|
1793145206.9293377
|
1793750006.9293377
|
|
52957
|
riskify by @evmaagca | Stardance
|
evmaagca
|
0
|
5.15
|
0
|
[]
|
[{"post_id": 71989, "ts": "202 [{"post_id": 71989, "ts": "2026-09-21T22:32:51Z", "hours": 5.1492, "text": "devlog 1: added the css style + the basic math for the grpahs + fixed like 3 bugs + readme done :(("}]...
|
Title: riskify by @evmaagca | Stardance
Devlogs:
Title: riskify by @evmaagca | Stardance
Devlogs:
devlog 1: added the css style + the basic math for the grpahs + fixed like 3 bugs + readme done :((...
|
1792731150.4049232
|
1793335950.4049232
|
|
9455
|
risuko-over-slack by @yuemiyuki4737 | Stardance
|
yuemiyuki4737
|
1
|
3.0
|
0
|
[{"ship_num": 1, "ts": "2026-0 [{"ship_num": 1, "ts": "2026-06-06T08:04:42Z", "hours": 3.0, "multiplier": null, "payout": 0, "blessing": "neutral", "text": "Made a downloader on slack… wait, its not as stupid as it sound, it use the high-performance download engine, RIsuko (created by me) to download content from the internet, compress it, and send it back to slack. The challenging part was actually making the bot reply, it is my first time making. a slack bot, while I do have experience on discord bot before, they are quite different. Really happy that everything works well after tons of debugging :)", "repo": "https://github.com/YueMiyuki/risuko-over-slack", "demo": "https://app.slack.com/client/E09V59WQY1E/C0B8MKR12G5"}]...
|
[{"post_id": 3366, "ts": "2026 [{"post_id": 3366, "ts": "2026-06-06T08:02:31Z", "hours": 3.4428, "text": "Made things work, added four comamnds: Help, ping, health check and download. Used a random image i found online to test, everything works really well :)"}]...
|
Title: risuko-over-slack by @yuemiyuki4737 | Stard Title: risuko-over-slack by @yuemiyuki4737 | Stardance
Devlogs:
Made things work, added four comamnds: Help, ping, health check and download. Used a random image i found online to test, everything works really well :)
Ships:
Made a downloader on slack… wait, its not as stupid as it sound, it use the high-performance download engine, RIsuko (created by me) to download content from the internet, compress it, and send it back to slack. The challenging part was actually making the bot reply, it is my first time making. a slack bot, while I do have experience on discord bot before, they are quite different. Really happy that everything works well after tons of debugging :)...
|
1793234888.2434444
|
1793321288.2434444
|
|
37185
|
riz! by @rizz | Stardance
|
rizz
|
2
|
2.0
|
2
|
[{"ship_num": 1, "ts": "2026-0 [{"ship_num": 1, "ts": "2026-08-29T06:12:14Z", "hours": 1.0, "multiplier": 2.4, "payout": 2, "blessing": "neutral", "text": "improved some major bugs and some issues related to riz-joke command and like some minor issues like it was just hosting the old code and i had to fix that and i had to revoke my api keys cuz i did some stupidity and that’s all for now", "repo": "https://github.com/STEVEALEX-source/riz-bot", "demo": "https://app.slack.com/client/E09V59WQY1E/C0P5NE354"}, {"ship_num": 2, "ts": "2026-08-23T16:57:10Z", "hours": 1.0, "multiplier": null, "payout": 0, "blessing": "neutral", "text": "Building Friday Bot was a wild journey. The hardest part by far was debugging why the bot was completely silent when I typed commands in Slack. Tracking down missing socket tokens and fixing that frustrating path error in systemd where Node kept looking for an index file instead of my actual server file drove me crazy for a bit. On the flip side, the most fun part was getting everything live on my Hack Club Nest server. Logging into the Linux terminal and realizing I could run a real backend app in the cloud felt amazing. Seeing the calculator command finally return a correct response in Slack after hours of tinkering was a huge relief and a ton of fun. I learned a lot along the way. I now understand how Socket Mode works without needing public webhook URLs, how to manage background processes using systemd so applications stay online around the clock, and how Slack apps handle permissions behind the scenes. It felt awesome to take code from a local project to a fully running cloud server", "repo": "https://github.com/STEVEALEX-source/riz-bot", "demo": "https://app.slack.com/client/E09V59WQY1E/C0P5NE354"}]...
|
[{"post_id": 56640, "ts": "202 [{"post_id": 56640, "ts": "2026-08-28T14:13:15Z", "hours": 0.7339, "text": "so i improved many thing like the joke command and the readme and many other bugs"}, {"post_id": 53120, "ts": "2026-08-23T15:30:56Z", "hours": 1.3189, "text": "Today was a wild ride building and deploying Friday Bot. The goal was to create a Slack bot that handles slash commands like math calculations and status updates while also backing it up with a clean Express API.Things did not go smoothly at first. Whenever I typed a command in Slack, it just gave me a frustrating error saying the app did not respond. I spent way too long digging into socket mode configurations. Turns out my app level token was missing the required connection scopes, and my local server was completely silent because nothing was actively listening down the websocket.Adding a global error handler to the code saved me. It finally surfaced what was breaking under the hood. Once I cleaned up the token permissions and wired up the Bolt app properly, testing the calculator command actually returned a correct result instead of timing out.After getting the code working locally, it was time to put it on the web. I decided to host it on my Hack Club Nest server. Logging into the Linux box via SSH felt like old school programming.Setting up the environment on the server took a few steps. I installed Node, cloned my github repository, manually created the environment file with all my private tokens, and tested it manually. Everything booted up on port 3000 just like it did on my local machine.To make sure the bot stays alive after I close the terminal, I configured a systemd service file. Now it runs as a background service with automatic restarts. Friday Bot is officially live and online around the clock."}]...
|
Title: riz! by @rizz | Stardance
Devlogs:
Today w Title: riz! by @rizz | Stardance
Devlogs:
Today was a wild ride building and deploying Friday Bot. The goal was to create a Slack bot that handles slash commands like math calculations and status updates while also backing it up with a clean Express API.Things did not go smoothly at first. Whenever I typed a command in Slack, it just gave me a frustrating error saying the app did not respond. I spent way too long digging into socket mode configurations. Turns out my app level token was missing the required connection scopes, and my local server was completely silent because nothing was actively listening down the websocket.Adding a global error handler to the code saved me. It finally surfaced what was breaking under the hood. Once I cleaned up the token permissions and wired up the Bolt app properly, testing the calculator command actually returned a correct result instead of timing out.After getting the code working locally, it was time to put it on the web. I decided to host it on my Hack Club Nest server. Logging into the Linux box via SSH felt like old school programming.Setting up the environment on the server took a few steps. I installed Node, cloned my github repository, manually created the environment file with all my private tokens, and tested it manually. Everything booted up on port 3000 just like it did on my local machine.To make sure the bot stays alive after I close the terminal, I configured a systemd service file. Now it runs as a background service with automatic restarts. Friday Bot is officially live and online around the clock.
so i improved many thing like the joke command and the readme and many other bugs
Ships:
Building Friday Bot was a wild journey. The hardest part by far was debugging why the bot was completely silent when I typed commands in Slack. Tracking down missing socket tokens and fixing that frustrating path error in systemd where Node kept looking for an index file instead of my actual server file drove me crazy for a bit. On the flip side, the most fun part was getting everything live on my Hack Club Nest server. Logging into the Linux terminal and realizing I could run a real backend app in the cloud felt amazing. Seeing the calculator command finally return a correct response in Slack after hours of tinkering was a huge relief and a ton of fun. I learned a lot along the way. I now understand how Socket Mode works without needing public webhook URLs, how to manage background processes using systemd so applications stay online around the clock, and how Slack apps handle permissions behind the scenes. It felt awesome to take code from a local project to a fully running cloud server
improved some major bugs and some issues related to riz-joke command and like some minor issues like it was just hosting the old code and i had to fix that and i had to revoke my api keys cuz i did some stupidity and that’s all for now...
|
1793234913.88267
|
1793321313.88267
|
|
63186
|
rizzy-alarm by @tejasv | Stardance
|
tejasv
|
0
|
2.1
|
0
|
[]
|
[{"post_id": 77509, "ts": "202 [{"post_id": 77509, "ts": "2026-10-07T08:56:07Z", "hours": 0.33, "text": "made pcb and gerber"}, {"post_id": 77393, "ts": "2026-10-06T19:43:40Z", "hours": 1.7714, "text": "made a blender design"}]...
|
Title: rizzy-alarm by @tejasv | Stardance
Devlogs Title: rizzy-alarm by @tejasv | Stardance
Devlogs:
made a blender design
made pcb and gerber...
|
1792439620.2319055
|
1793303620.2319055
|
|
25644
|
rlbuddy by @kmdw | Stardance
|
kmdw
|
3
|
185.95
|
2643
|
[{"ship_num": 1, "ts": "2026-0 [{"ship_num": 1, "ts": "2026-09-28T00:20:30Z", "hours": 40.0, "multiplier": 18.76, "payout": 740, "blessing": "neutral", "text": "I made rlbuddy, a rocket league companion app! This is the third ship so far. The big features this time are the asset swapper and Linux support, but I think that the feature I’m most proud of this time is the asset swapper! It inserts the appearance of one item into the file of another, meaning by swapping out assets for others you can make your car look however you like! (Client-side only, of course). I learned a lot about byte-level manipulation and working with raw data streams and encryption/decryption in Rust, and I can read hex (relatively) well now! It’s also great because I think this combined with the doorstop experiment from a while ago gives me a nice base to try some reverse-engineering challenges in the future. I also worked a lot on performance this time, such as tuning loop times/ui fps caps and minimizing ram usage (I achieved around an 80% reduction here!) There are a crap ton more features added than that (totaling almost 100 commits), but I’m pretty sure you’re not supposed to have massive feature-dumps on the ship text. So the full list is on the latest github release! In the image, you can see I have a white fennec. In reality, I don’t actually have a tw trim, and the equipped decal isn’t compatible with the fennec either. But, using the power of asset swapping, I can get a beautiful all-white car. Neat!", "repo": "https://github.com/Kalilamodow/rlbuddy", "demo": "https://github.com/Kalilamodow/rlbuddy/releases/latest/"}, {"ship_num": 2, "ts": "2026-09-07T20:33:21Z", "hours": 27.0, "multiplier": 16.74, "payout": 457, "blessing": "neutral", "text": "rlbutddy is a feature-rich companion for Rocket League, and v1.2 is out. There’s been some big architectural changes meaning it’s faster and bug-free-er, as well as big new features! New!!! Avatars! Now you can see people’s profile pictures even when you’re not on their platform. Custom map loader! You can import maps from a ZIP or download them straight from Bakkesplugins! Gamepad overlay! Show your controls while playing, to show off your bindings or get some help with your speedflips. I think I’ve definitely improved at Rust since the last release. Just in terms of the amount of mistakes I make, I feel like it’s way more smooth sailing than before. For example, I was able to (mostly) write not-so-buggy code and “run the borrow checker in my head” without needing to cargo check a million times, and clippy tends to have a lot less suggestions for me. Pretty great!", "repo": "https://github.com/Kalilamodow/rlbuddy", "demo": "https://github.com/Kalilamodow/rlbuddy/releases/latest/"}, {"ship_num": 3, "ts": "2026-08-25T22:08:36Z", "hours": 109.0, "multiplier": 17.59, "payout": 1446, "blessing": "neutral", "text": "rlbuddy is a feature-filled companion for Rocket League! It shows the skill of everyone in your lobby and has tons of toggleable widgets and features to make your life a lot easier, from Discord integration to music control. This is my first non-tiny Rust project, and I think it was really great! I’ve learned a lot such as proper state management and the ins and outs of Rust’s syntax. You can give it a try by heading to the Github releases section. If you play Rocket League, I hope you use it!", "repo": "https://github.com/Kalilamodow/rlbuddy", "demo": "https://github.com/Kalilamodow/rlbuddy/releases/latest/"}]...
|
[{"post_id": 80191, "ts": "202 [{"post_id": 80191, "ts": "2026-10-16T17:13:05Z", "hours": 3.4819, "text": "more small improvements. i gave the session stats widget an actual service as well which speeds things up a lot since its not recalculating statistics every single frame. the swapper also uses the backup file if one is available and it no longer accepts duplicate custom maps"}, {"post_id": 77292, "ts": "2026-10-06T13:38:54Z", "hours": 0.4772, "text": "A few fixes here and there. Some refactors, caching externally loaded playlists in savedata, and fixing a deadlock when clearing the items cache. And for some reason the decal option was missing from the items list picker."}, {"post_id": 75881, "ts": "2026-10-01T13:48:20Z", "hours": 5.9928, "text": "I made a standalone web-based swapper for the people who don’t want to download an entire app. A lot of people had broken packages after the last update but didn’t want to install a dedicated program just for a few swaps. So, I used the asset swapper backend of rlbuddy to make this thingamabob which runs entirely in the browser! First, I had to find out how to actually share rlbuddy’s asset swapper with other projects. Turns out Cargo has a cool thing called workspaces where you can have multiple crates in the same repo! So I moved the dedicated UPK/AES etc. stuff into a new crate and added it as a dependency in regular rlbuddy. Then, I whipped up a small wasm-compatible eframe app with the template, and included the new crate as a dependency. I made a big state machine of the steps meaning it’s impossible to have an invalid state, which is really nice. The only problem was that Rust’s ownership system isn’t smart enough to deal with it, so the code is a bit repetitive in places but it’s definitely worth it. The WASM restrictions were super annoying, mainly do to with filesystem access and requiring async. I ended up using the egui-async crate and spilling Binds all over the place within the state machine. I couldn’t even figure out how to get the actual swap procedure to run in a different thread. I also spent like 30 minutes debugging why in the world asset swapping was just broken. Even when I verified the same binary data were going in to the Upk object in the webapp and rlbuddy, for some reason the output upk was just different. Turns out I used chosen_replacement.name instead of chosen_replacement.package.name() . Isn’t that great? Anyways, it works fine now and I’m planning on posting it on r/RocketLeagueMods and possible r/RocketLeague."}, {"post_id": 74679, "ts": "2026-09-27T23:46:54Z", "hours": 3.0297, "text": "Not much in terms of super-cool stuff this time. Highlights are: make sure dynamic playlist loading uses the latest cache file (and also fix its loading on normal people’s rocket league installs) loading the cache file on another thread so it doesnt freeze the whole app for a second on the first lazy lock access searching for items in the item swapper moved the error handling of the map loader to anyhow So no ultra-exciting things to talk about. anyhow is really cool though, it really simplifies error handling everywhere in the app, and I’m a big fan of its bail! macro. I’m also using Cow a lot more. Moo!"}, {"post_id": 73444, "ts": "2026-09-25T03:22:46Z", "hours": 4.635, "text": "I got licensee version 34 (encrypted payloads too) to swap finally! Okay, so this was awesome. Rocket League’s been having a bunch of updates lately, which breaks the packages of people trying to get free stuff. So, in a Reddit thread, I ended up dishing out a bunch of swapped asset files for people in this situation. There’s a new boost which everybody wanted really bad since it was really rare (but they decided to make it unlockable at rocket pass lv99 so it doesn’t even matter anymore), and it was UNSWAPPABLE for three reasons: it was encrypted specially i hadnt encountered specially encrypted packages meaning their handling was wrong aes keys werent out for it yet (or so I thought) I’ll start with 3. I had written a tool which looks in the games cache for AES keys, and dumped them, and it worked great. What I didn’t expect is the filename to change between versions. So, I was super confused since yesterday since the AES key for the boost was just… missing. Thankfully, the geniuses at the RLMM discord got it and helped me figure out that the file I was reading from was just outdated. Great! Anyways, not too big of a deal, just a big delay. Now it’s time for the real challenge: figuring out how the hell to handle this type of package. First, I did some research. The twelve bytes of padding in files with licensee version >=33 before the summary are actually a nonce/aes iv, not just padding. Then, the header is encrypted with AES-CTR with the twelve first bytes of the iv just being the nonce. And the compressed chunks are encrypted too here: each one is also AES-CTRed with their own nonces in FCompressedChunkInfo. Thing is, I don’t have an implementation to really go off anymore, meaning there’s a lot more trial and error. For the original implementation, there were one or two existing asset swappers (though they were vibecoded meaning more of a puzzle to figure out what they’re even doing), but this time I was mostly relying on emii’s fork of UEViewer. Anyways, after spending a while figuring out what exactly I had to masquerade (payload, key, swap nonces) I finally got it to work!"}, {"post_id": 72491, "ts": "2026-09-23T02:26:44Z", "hours": 1.8381, "text": "rlbuddy now loads additional playlists straight from the game’s cached webconfig, instead of hardcoding them all! This means that the list of playlists will basically never go out of date. There are actually quite a few services which rely on game files (eg. auto stats api setup, custom maps, asset swapper). So, the first thing I did was to refactor it so make all of these load from the same global value. Then, for playlists, I made it read from a specific file in the game’s http cache directory which contains a bunch of configuration. Then, it parses it into a JSON object, grabs all the values with Class PlaylistSettings_TA, and then extracts the title and playlist id! There are still a bunch of built-in Playlists, since those have internal things like going from a casual playlist type to its respective competitive one or having the hard-coded list of singleplayer playlists. But, all the other ones, like ltms are dynamically loaded. Yay!"}, {"post_id": 72027, "ts": "2026-09-22T00:56:20Z", "hours": 3.9097, "text": "I made a ton of improvements to the asset swapper! And fixed a bug with the music player which is cool technically, so I’ll start with that. The player uses the GSMTC api, which is async, but egui doesn’t really like async. So, I made it so that every time you wanted to run some command on the current session, it would start up a new thread. This was less than ideal for a few reasons: terrible performance race conditions could happen just… in Windows itself The main problem was 2: for example, if you skipped a replay too fast, the Play command from the end of the replay might actually reach Windows before the Pause command from the replay start, leaving your music paused. So instead, it starts one dedicated worker thread which has a channel which receives a Box<dyn FnOnce(Session)> , and executes at most one command every 50ms (only one command per loop, fixed loop time of 50ms). It’s really interesting because you actually end up sending a closure through a channel, which feels funny but when you really think about it it’s just ownership semantics. But, that’s working great now! On to swapper improvements! First, I moved most of the swapper module to anyhow handling (it’s heaven!), and I also modernized the upk api a bit since it was basically dragged directly from the proof-of-concept repo, meaning it was not really application-ready. Also, it previously looked at the game’s webcache to decode the AES keys for header decryption, but now it uses the wonderful Toga-Files repository along with a really nice caching layer for it to load items and their associated API keys. Having access to the actual item data is also way better since you don’t have to manually type in a package name which is kinda weird for any normal person. Now, you can select it from a dropdown instead! I love UX."}, {"post_id": 71420, "ts": "2026-09-20T19:51:05Z", "hours": 4.5228, "text": "rlbuddy now has item swapping! Basically, making one item appear like another to use items you don’t actually own. These hours were spent on a few different things. Firstly, I optimized the thing a bunch! There were a few places that were pretty slow since the original repo was mainly for testing. For example instead of decrypting the entire header and rechecking it to find the right key, it only loads the first 4 blocks and attempts to decrypt the first FString. If it fails, that means the AES key isn’t valid. Also, I improved the error handling with anyhow. It actually makes it so much easier to deal with things and was pretty useful when debugging the move over to the actual app. Now I’m just going to work on polishing the panel UI a bit more, since it’s still kind of ugly."}, {"post_id": 70888, "ts": "2026-09-19T23:20:36Z", "hours": 2.1058, "text": "Apparently there are a bunch of different AES keys rocket league uses, not just one. You can get a list of them from the psynet config url, and then do some weird crc stuff to change them from “content keys” to aes keys. I discovered the rocket league mapmaking discord today and I’m blown away by the amount of information on there, and also apparently my whole thing about the unknown bytes when licensee version > 32 was straight up wrong, which is funny. Anyways it works for all items now, which is great. I’m gonna work on actually putting it in rlbuddy now, hopefully that goes well"}, {"post_id": 69896, "ts": "2026-09-18T01:01:05Z", "hours": 0.9667, "text": "IT WORKS WHEN THE HEADER EXPANDS NOW YAYAYYAYAYAYYAYAYYAYAYAY I’m gonna be honest, the problem might just have been that my testing package was corrupted. I was debugging my deserialization logic, which I thought might have some parsing issue near the header extraction, and noticed that once the encrypted region deserializer’s Cursor reached the end of the region, it was actually FARTHER than the package summary’s total_header_size - by 6 bytes! That’s pretty bad, and I don’t know how something like that even happened. Anyways, I repaired the game and brought over the shiny new upk and… it worked. I’m pretty sure that I only accidentally did this while tidying up my workspace a few hours ago, but if this has genuinely been the thing holding me back for the past few days - I also finally made the poc code public, at https://github.com/kalilamodow/rl-upk-testing/"}, {"post_id": 69850, "ts": "2026-09-17T22:15:40Z", "hours": 1.67, "text": "IT SWAPPED IT SWAPPED IT SWAPPED AHAHAHHAHAHAHHAHAHAHAHHA FINALLY Okay now I gotta make it work for other pairs of boosts. If WHEN that works, I’ll actually put it in rlbuddy. I removed all the code relating to changing offsets to try to give myself a minimal workspace to tinker in, with as little side effects as possible. So, all the current code does is 1. open them 2. replace the name table entries and package guid. Surprisingly simple. Once I make it a bit more generic, I can extend it to support more swaps. If I’m really unable to get it to work for more combinations, I’ll probably end up just publishing the poc repo and not adding swapping to rlbuddy at all. This is a great motivation boost, though. I was honestly dreading coming home to work on it today because I was done with failing over and over, and considering giving up."}, {"post_id": 68661, "ts": "2026-09-15T14:25:29Z", "hours": 2.6667, "text": "I got a name swap! It doesn’t work properly, but I’m glad the game didn’t crash. These hours have been by far the most frustrating because the only way to test is against the real game since it’s really picky, but the game takes like 30 seconds to start, and if it crashes, it gives a cryptic error log, and if it freezes, it doesn’t give any log at all. I spent most of the time messing with moving different offset values forward and backwards, and ended up reverting to a simpler version with no overwriting when the header size changes just to test things. So far, I’ve mostly gleaned that name renaming is possible, but only up to a point. If the name grows too much, the game crashes, and if the name shrinks too much, the game freezes. Additionally, for some reason, total_header_size changes by the padded header size instead of the encrypted header size, which I still don’t understand. It’s seriously weird. I serialize each name properly, and yet the game reads it wrong. This game really does need a new codebase. When UE6 comes out, either the game’s gonna be so good it doesn’t need mods, or it’ll be relatively easily moddable. Or neither, which is the most probable based on Epic Games’ track record. Anyways, enjoy the beautiful screenshot."}, {"post_id": 68281, "ts": "2026-09-14T20:49:26Z", "hours": 3.1181, "text": "I figured out what comes after export tables! And it took way too long, since I DISCOVERED IT MYSELF! At least, as far as I know. I couldn’t find any information online about this portion of the header, so it was basically luck that I noticed a pattern. Before, I was just copying everything after the export table and pasting them back in afterwards, but as you can guess, that stopped working if I adjusted anything within the encrypted region. Compressed chunk info is a known part, and I figured out the AES padding pretty easily, so most of the time was spent trying to figure out what in the world those 24 bytes were. The first four bytes in the compressed chunk info are how many chunks they are. Here, in the Bubbles boost package, there are two (little endian), with 24 bytes of ???. And in Flamethrower, there are three, with 36 bytes of ???. I noticed that the ??? was always n chunks * 12 bytes (actually, it’s lucky that I didn’t test any which ended up having 0, otherwise I probably wouldn’t have figured it out). Since this probably had something to do with chunks, I made a guess that each chunk had some extra data stored in this array, 12 bytes of info per chunk. Using HxD’s highlight-bytes-and-get-the-decimal-value feature, I noticed a pattern where the first 8 bytes looked like an i64 offset and the last 4 looked like an i32 size, which the compressed chunk infos were also like for their matching counterparts. So, I deserialized it as [i64; i32] * n chunks and was able to read it better. These additional chunk infos seem to kinda sorta match up to uncompressed header offsets, but I don’t really understand what they are (and they’re marked as such in the code, lol). So, with that working (and some fiddling with broken serialization of TArrays which I’m surprised I didn’t catch earlier), I now understood the ENTIRE package header! Hooray! Let’s look at another example. body_grain_SF, the Fennec, has 4 chunks. Guess how many bytes of ??? it has. That’s right, zero! For some reason, a few packages just don’t have those things. After going through a bunch of them, I found that if licensee version was 32, there was no ???. But if it was 33, there WAS ???. I basically just added an if statement and it was fixed. I’m going to try to work on figuring out the padding in between the package summary and the start of the name table, but if I can’t, I suppose I’ll finally get to actually doing package swapping. Later!"}, {"post_id": 67059, "ts": "2026-09-13T03:19:34Z", "hours": 2.9378, "text": "I JUST DESERIALIZED AND RESERIALIZED A UPK. OMAIGOD IM SO HAPPY. my brain is so tired. Alright so the payload still doesn’t get transferred but that’s pretty simple, it’s just Vec::extend. What a challenge! Deserialization actually ended up being the easy part, which is opposite of what I expected from starting the project. It went (relatively) fast since there are a few existing implementations which I could kinda just “borrow” structure parsing from. But, mine is unique in that it almost completely reserializes the package itself. I might’ve been able to “borrow” reserialization, but the impl I’ve been looking at is STRAIGHT ai slop and it’s more annoying to read that than to just do it myself. It was useful though, so I have to respect it. The first problem: basically, the first byte of an FString determines how many parts it has, NOT how many bytes it has. Which wasn’t a problem for parsing, since I had some word-based helpers which worked so I didn’t think much about it. But, when reserializing, I made two mistakes: 1. forgot to add the null terminator (which shouldn’t even be necessary) and 2. I accidentally put the wrong offset. I have no clue why there’s a null terminator for FStrings. The format is [n chars][the char bytes], meaning it should already be known how long the string is. Pretty stupid, but whatever. Anyways, when reserializing you’re supposed to include the null terminator in the string length marker. So, my logic was that I should offset it by two since unicode characters are two bytes. Bleh! I should’ve only incremented by one, since it’s in terms of utf16 chars rather than bytes. Yay, that worked! I don’t know how to summarize the next problem, so I’ll explain the reasoning. The package header is file summary + tables + something else (we’ll get to that later). Anyways, there’s some mysterious padding in between the summary and tables. Parsing, it’s easy to skip over because the summary includes the tables start offset ( name_offset , the name table is the first one). But when reserializing, you gotta keep those offsets intact. Well, I’m not totally sure if you actually have to keep them, but just for the sake of simplicity I tried to (which ended up adding more complexity, which I’ll see if I can remove now). So, I serialize/push the summary to the big Vec. Then, I make a new Vec and serialize the name table at (name_offset - summary_size), since the new Vec has to be encrypted and then appended to the big Vec. I do the same thing for imports/exports (placing the table at import/export _table_offset - summary_size ). Oh no! Since name_table_offset isn’t equal to summary_size, there’s some padding in between, which is expected. HOWEVER! The previous logic would skip over it in the table Vec creation, meaning the table Vec would have some 0-padding at the start. This padding needs to be in the SUMMARY part instead, and the name_offset should basically be the ZERO of the tables Vec! I doubt I would’ve been able to solve this part without the beautiful tool HxD . It’s really easy to use and is always helpful when I’m figgling around with binary. So, it does some more math, calculates the spacing, adds it to the big vec, and removes it from the tables Vec. Then, it re-encrypts the tables Vec and pushes it onto the big Vec! Okay, now that’s done. NOPE! There’s a mysterious structure for some reason at the end of the real table part? Yep, it’s that something else I said we’ll get to. I actually still have no idea what it is. From some research, I gathered it must be the dependencies table, but it’s really poorly documented. So, I ended up just cutting the bytes out from after the export table to the end of the tables part, and “pasting” it back before re-encryption. I do hope this devlog was readable. Now I’ll get to including the actual package payload and hopefully simplifying reserialization offsets. See you soon!"}, {"post_id": 66867, "ts": "2026-09-12T20:09:08Z", "hours": 2.0211, "text": "I’ve been putting this off for a while, but I’ve finally decided I’m going to add asset swapping. There’re a few existing implementations, so I’m reading those as well as some pdfs about the upk format. So basically credit to this document and this implementation . All it can do for now is parse the file summary. The implementation wasn’t too bad since I had Google as well as the C# implementation I just talked about. I basically have trait Deserializable : Sized { fn deserialize ( reader : & mut impl Read ) -> AnyResult < Self > ; } Then implement that for the various parts (eg. FGuid , a generic TArray ). Then, I use byteorder to read out numbers into each struct. For example, #[derive(Debug)] struct FCompressedChunkInfo { uncompressed_offset : i64 , uncompressed_size : i32 , compressed_offset : i64 , compressed_size : i32 , } impl Deserializable for FCompressedChunkInfo { fn deserialize ( reader : & mut impl Read ) -> AnyResult < Self > { Ok ( Self { uncompressed_offset : reader .read_i64 :: < LittleEndian > () ? , uncompressed_size : reader .read_i32 :: < LittleEndian > () ? , compressed_offset : reader .read_i64 :: < LittleEndian > () ? , compressed_size : reader .read_i32 :: < LittleEndian > () ? , }) } } I originally prototyped it in Python and then moved to Rust once I got tired of python’s byte shenanigans."}, {"post_id": 66219, "ts": "2026-09-11T22:03:11Z", "hours": 2.5883, "text": "I did lots of performance improvements and also IT WORKS ON LINUX NOW!!! Let’s talk about performance first. Anyways, it has been using around 4% and 220MB of ram, which isn’t really what you’d expect from an overlay! So, I used things like DHAT to profile the memory, and noticed that fonts were doing some shenanigans. So I removed egui_system_fonts and loaded directly from C:\\Windows\\Fonts\\segoeui.ttf and RAM usage dropped to 30mb. That was what the previous devlog was about. I then changed the default logic update rate from 10ms (100/s) to 65ms (15/s approx). This reduced idle cpu usage to under 0.2%. But, doing anything with the UI would spike it to 4%, which is still pretty bad. So, I used a method I found in an egui issue thread where I’d count the frametime since the last render, then sleep until the target frametime was finished. For example, if the frame rendered in 2ms but I want 16ms frametime, it would sleep for 14ms. That way, I could limit how often the UI rendered! This made it stay under around 1% CPU forever! Okay, Linux time! Rust is mostly cross-platform, so support was mostly dealing with dumbass design decisions. Since Music relies on GSMTC, I had to feature-gate the entire thing (as well as the windows/windows-future crates) behind cfg(windows) :( I also had to fix Linux font loading (since like I said earlier, it’s hardcoded to segoe ui). So I install fontdb on Linux and search for some popular fonts from there to load instead. Finally, I fixed up the storage paths since it could only load from AppData. I also made it use AppData\\Local vs. AppData\\Roaming instead of putting everything in Roaming. I first got it working through WSL! ! It took some messing with my GPU drivers but I got it to render. Now was the real challenge: getting it to build on an ACTUAL Linux machine. I had an old, basically broken Surface Pro 3 that I had put Debian+GNOME on for shits and giggles a few months ago. The perfect target! The main problem was getting Rocket League to even launch. I set up Legendary (goated launcher btw, check it out ) and tried to run it. I installed Wine, got Wine Mono, and it failed with a weird cryptic error about the CreateWindow function. The obvious solution to a graphical error: disable EAC. Yay, it launched! Then to get it to build. First I had to fix some filepaths since Linux is case-sensitive (nerds) while I don’t care about case on Windows. Then, rust-lld gave some weird error about simd or something which I didn’t care about. I just ran cargo clean and rebuilt it and it worked fine. Then to run, and it actually worked first try! It’s cool because I went into it expecting to debug a million socket errors, but it actuallly connected and ran. Awesome sauce! Now Linux users can use rlbuddy. More minions to contr- I mean cool users! Cya in the next one! ;) If you check out the rl perf overlay in the screenshot, you’ll be able to guess why this is the laptop I decided to put Linux on."}, {"post_id": 65417, "ts": "2026-09-10T15:25:20Z", "hours": 0.8867, "text": "I’ve been debugging high memory for the past month or so. It was egui_system_fonts. Why would fonts take 170mb of ram????????????????????????????????? help!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! I hate external dependencies!!!!!!!!!!!!!!!!!!!!11 I owe my life to DHAT now. go star https://github.com/nnethercote/dhat-rs"}, {"post_id": 65067, "ts": "2026-09-09T22:28:11Z", "hours": 1.5, "text": "More gamepad overlay buttons!!! Yay!!! In not-so-awesomely-exciting news, I also added a table of contents to the readme (as well as a generator for it). The script’s in Python (I’m sorry for not using Rust) meaning the repository is now 0.5% Python. Takeover incoming? I overcomplicated the script a lot, but it’s really easy to read. There are three whole comments, aka 6% of lines in the whole file! It’s not technically interesting in the slightest. I was thinking of building a tree-like structure before outputting the string, but realized I can just use the header level (h1 vs h2 vs h3 etc) as the amount of spaces / 2. I also refactored it not to use magic PanelId s for the default panel list. I kinda rushed that part when I made it, I’ll be honest. But now each Panel can implement fn open_by_default(&self) -> bool (returns false by default) and if it does it keeps it open when savedata doesn’t exist yet. I don’t get how an entire hour n 27 minutes was spent on like 7 not-even-that-big commits. At least it was a good use of my math class. We reviewed limits for some reason (which everyone should already know for one because they took the prereqs?), and the teacher managed to extend her lecture for the ENTIRE class time. rlbuddy development saving the day, I guess (just like it will in your rocket league games btw, so you should download it)"}, {"post_id": 64822, "ts": "2026-09-09T14:32:38Z", "hours": 1.4469, "text": "I’ve mostly been working on smaller features, improvements, and bugfixes. A cool one I did is fixing weird glitchy movement with the gamepad overlay window! Basically, the window needs to persist its position across disabling it/enabling it. And egui is immediate mode. So, every time it rendered the viewport, it would both set and get its position. So, moving it would update the current position, but because of the weird interop between setting the position in a ViewportBuilder and getting it from the viewport info, it would glitch back and forth, especially when my laptop is lagging. I fixed it by only calling the ViewportBuilder ‘s “ with_position ” method when the overlay had just been enabled ( !was_enabled && currently_enabled ). That way, it only sets it once externally, and lets it move freely while it’s open. Then, while it’s moving, it continues to update the saved position. And upon closing and then reopening it, it sets it again (but only once!) Relatively simple fix but it’s kind of funny. I mainly noticed it when I added a feature that would auto-unmaximize the overlay when the user maximizes it, and it would go crazy and fly around the screen. Very fun! I also fixed a pretty old bug where it would incorrectly detect the local player. Basically, the stats api doesn’t actually tell you who the local player is. So, we have to guess based on who the camera is targeting right now. Usually, it’s on the local player, but during goal replays or spectating a bot when joining a casual game, it would be improperly set at the start and then stay like that. So, I made sure that it always updated as long as the match is 1. in progress 2. not in a replay. It also fixes a thing with inconsistent match toasts, since the toasts only fire if the scoring player is the local player (and if it thinks the wrong player is the local player, everything’s messed up). I’m planning on revamping hotkeys next, to allow you to bind to any controller button and even do custom quickchats in-game. Later!"}, {"post_id": 63368, "ts": "2026-09-07T04:36:35Z", "hours": 1.4131, "text": "Time formatting doesn’t suck anymore! I made the implementation way better so I could make it support stuff even past weeks, like months and years. The new implementation added around 60 lines, but it’s for a good cause. The previous implementation used a match statement with duplicated logic that only supported seconds, minutes, and hours, using a pluralize function and inline if-statements everywhere. If it were sent to any rational code-reviewer, they would likely have a heart attack. Now it’s a bit more complex but less complicated. You have TimeUnit s which format the unit themselves and TimePart s which format a value + a unit. Then, just use some math to build a Vec and format it into a string. Boom! Beautiful, well-formed time formatting. To soak in the new beauty, I added days n weeks. I just had to add 14 lines! The previous one would have probably needed a bunch of stupid magic numbers and weird chains everywhere to accomplish the same thing. Okay, I’m done using your time on reading a way-too-long write-up about fixing a crappy implementation that shouldn’t have been there in the first place. I’m gonna ship right after posting this. If you get it, make sure to give it 10 stars in everything 😉. Haha, jk. Well, not really. Cya later."}, {"post_id": 62559, "ts": "2026-09-06T00:29:40Z", "hours": 1.8697, "text": "Had to remove the tracker.gg widget :( unfortunately they don’t allow scraping and what I had wasn’t allowed anyways. Things that used to open the player info widget now just open tracker in a browser."}, {"post_id": 60965, "ts": "2026-09-03T17:23:52Z", "hours": 2.9253, "text": "I finished the big refactor! I pushed around 25 commits to GitHub at once. I’m really gonna enjoy adding the next feature, basking in how much easier it is ;) Apparently, I never made a service for session stats, so it currently can’t persist its one setting. Actually, writing this I realized that the setting isn’t actually used, so I can delete it. That’s great. After finally kicking out all the annoying legacy panel code, I remembered that I never re-implemented the positioning or its related persistence code for the new panel system. The persistence system was reworked too, something that I don’t think really got included in the previous devlog. Each service can implement save , and there are generic helper methods in a common module for saving/loading serde structs to a file in the app data directory. The actual app now uses this method as well instead of doing all the pathing logic itself, so the persistence module is pretty small and could honestly be moved into app.rs. For new positioning, I store a list of open panels. Weirdly enough, it’s actually a lot easier than adding a position/is_open field to every AppPanel. So, I run the name through a Hasher to make a u64 which is easier to clone/copy and stuff and made a newtype around a PanelId. That way I could reuse the code from the previous paneling system to swap/manage open PanelIds, then just use find on the AppPanel vec to find the corresponding widget. Also, I never mentioned it but I’ve been trying RustRover for the past few devlogs. It’s a lot more stable than Zed, but I sorta miss its snappiness and minimalism. If I end up finding an annoying bug in RustRover, I’ll probably switch back. However, it’s been quite a few days and I don’t think I’ve found a single one. JetBrains is truly awesome at tooling. See ya next time. Probably will do my next ship soon :)"}, {"post_id": 60552, "ts": "2026-09-02T23:31:23Z", "hours": 3.3072, "text": "I’m MASSIVELY refactoring rlbuddy! This, I think is the second (and hopefully the final) major refactor to the codebase. So, basically the current architecture is split into services and widgets. Each service has an update method, which does its stuff (draining subscribers, processing commands, etc). Since egui is immediate mode, this architecture works unlike callbacks/event-based which I actually tried to implement but couldn’t. Every widget is just implements egui::Widget, that holds some state handles (Rc) and command senders (Sender), usually just one for its respective service. Services are alive for the lifetime of the app, while widgets are the openable panels you see. Widgets display the data from service state, and talk back to it by mutating shared state or sending commands. I think music control has been the best for me to wrap my head around to decide about changes, so I’ll explain it with that. Music control had a controller, a service, and a widget file. The controller was a thread pool manager (if you can call the questionable implementation that) around Windows’ GSMTC API to give a single-thread api for it (since the app is basically single-threaded). Then, the service is always alive, syncing a state struct between the controller and its own state handle. Finally, the widget is optionally there as a panel, with next/previous/pause buttons which send commands and the ui which reads service state to display. It also has a mutable service state reference for settings (the “pause during anthems” button). The god object RlbuddyApp struct stores all of this stuff. So it has a music_control_service, music_control_widget, stats_api_service, all of those fields, which kinda sucked. On top of that, I had to maintain an enum of every panel, which was enumerated for the “open panel” popup and to render each one if it’s open. you might think “hey… isn’t that a self referential struct? how does music_control_widget store a reference to music_control_service?” Well, music_control_widget internally only has fields for the music_control_service’s state handles which are Rc/Arcs. So it only needs a reference to the service for it’s Widget::new function lifetime, all is well. Although it doesn’t look horrible, it was HELL to do anything. For example, refactoring the gilrs service out of hotkey and adding the gamepad overlay probably involved just as much fiddling around with Rcs in RlbuddyApp as adding the actual feature. So, I decided to move to a new architecture: services + features! And, after thinking about it for around 8 hours (i slept), I decided on something a bit different. Services + panels! Yeah, basically the same as services + widgets, but I was gonna implement it GOOD this time, and make design decisions that wouldn’t bite me in the ass later on. I wrote my first traits - Service, which requires update and optionally save , and Panel , which requires name() -> &'static str and ui (same as egui::Widget: :ui). Then, I’d change how components are registered: instead of keeping every service and widget as a field in RlBuddyApp, I’d make a Vec<Box<dyn Service/Panel» for each, and loop through it. I spent about an hour trying to get Panels to automatically implement ui by requiring egui::Widget as a supertrait, which doesn’t work since widgets are stateful (need &self). So, I tried to require &mut Self to implement egui::Widget, but apparently something to do with type erasure doesn’t let that work with dynamic dispatch which was needed for the Vec<Box> thing. I’ve moved a few features to the new architecture, but I’m still in the process of finishing up. Additionally, it has to work side-by-side with existing crappy monster-Panel-enum architecture, which sucks to maintain. Okay, once again, I had to strip down a ton of detail (and my terrible wording but wtv) cause I hit like 5000 characters b4 realizing. See ya! May the Stardance gods increase devlog limits :)"}, {"post_id": 59926, "ts": "2026-09-01T23:19:29Z", "hours": 2.5164, "text": "rlbuddy has a gamepad overlay now so that you can show off your awesome bindings! Don’t even bother figuring out mine. It’s kinda starting to become hellish to add new widgets/services, but I’m almost done for the next ship so I doubt I’ll refactor just yet [edit: im doing a super refactor now hehe]. I made the overlay with the raw egui::Painter rather than actual controls (since it would be pretty hard to make a controller out of Buttons or something). I also prefer to avoid embedding static assets, otherwise I might have made an actual one in MS Paint or something. I also can’t seem to find a good egui version. I’ve been on 0.34 almost the whole time, which works well but doesn’t support transparency in extra viewports. But, 0.35/0.36 broke external viewports. So, until then, we get an opaque gamepad overlay :( but the main app is still transparent, so it’s not horrible. Rolled an 8 and a 9 on the Stardance rng today! I’m so proud of myself."}, {"post_id": 59228, "ts": "2026-08-31T21:59:04Z", "hours": 0.4561, "text": "I moved the mmr server to alpine linux! Before, it was regular raspberry pi os lite on the Pi. But, it was behaving strangely, randomly rebooting and things, so I decided to try installing Alpine. The lightweight distro has been on my radar for a while now, but this was my first time ever actually trying it. I imaged it normally with the raspberry pi imager, and put it on the pi, did setup-alpine, and all was well. But, I put in the wrong wifi password at first, which broke everything else. I tried changing the wifi config and doing something with wpa_supplicant (thanks google ai overview), but nothing fixed it so I ended up doing a fresh install. This time, I put in the right password and everything went great. I set up ssh, node, pm2, etc. and moved the server files onto the pi through sftp. Then, I got it running over my local network and set up authentication. So, it was time to set up the Cloudflare tunnel again. I followed this neat guide , changing values as needed (for example, my pi is aarch64, but the guide was x64, so I spent a while figuring out why the pi kept giving elf errors). It took a really long time to set up the openrc startup script because 1. cloudflare rotates the setup token relatively frequently and 2. I accidentally put 2 –token arguments. And since I was using vi, I didn’t even notice and kept trying to figure out why cloudflared crashed on startup. But I eventually found out and removed the duplicate and it worked! Then I finished setting up pm2 and put the pi in its home next to the router, and mmr.kmdw.dev is back up exposing your lobby’s skills. See you in the next one! Yes I know part of the token is in the image but it’s used already so it doesn’t matter"}, {"post_id": 56962, "ts": "2026-08-29T00:27:50Z", "hours": 9.075, "text": "rlbuddy can load custom maps now! Either you can download them and then import from a zip, or you can download them from bakkesplugins straight from the app! Importing it from a file is relatively easy. I checked the code for Lethamyr’s custom map loader to see what exactly it did, and for Lethamyr maps it: extracted the zip into a temp dir read info.json, .udk, and preview.jpg copied those into its own saved map directory I knew Rust, being a low-level language would let me not have to extract the zip fully first. Additionally, Bakkesplugins maps don’t contain info.json or preview.jpg, they only contain a udk file, so I had to make sure it would work with that. So, the import code I wrote basically: enumerates files in the zip extracts info.json (if found) into a struct copies *.upk into the target directory copies *.jpg into the target directory/preview.jpg if it exists It worked great. I made a really cool looking widget with stupid manual layouting behind it for map selection. First, it would add the preview image (or a black background if none). Then, it would get the screen rect of the image, and draw a semitransparent black background onto it (for contrast). Finally, it drew the actual contents in that screen rect. So, I got pretty-looking tiles with preview image backgrounds! I also added a progress bar for import status. I benchmarked each main part of the import process and found that decompressing/extracting the upk file took around 2 seconds, for which the ui thread would be totally frozen (big nono)! So, I moved the work onto another thread, and also made it decompress/load in 100 total chunks, updating a progress bar each time. I then moved on to the map downloader. Bakkesplugins doesn’t have an API, so it relies on scraping the website. I didn’t actually use any external crates for gathering information, though. I wrote a parser which takes a list of rules, each with a start and end search string, and looks for each of them in sequence till it fails to find a rule. I tried to make it completely zero-copy, but something it needed was to turn an [Option; N] into a Option<[T; N]> if every element was Some , but the only way I could figure out how to do that was with an iterator. It’s still really fast though. So, it parses search results, including map titles and urls straight out of the html string without needing an HTML parser or regex library. Once it was able to load results from the search page, I went to sleep and came back today. Now I had to get it to actually download the ZIP files and preview images, and import them straight into the app. It first has to download/parse the plugin info page since the ZIP url isn’t available in the search results. Then, it requests the ZIP. I had some trouble with this, since I wanted to show a progress bar for the download status. I first tried a function from this gist , but it didn’t work. So, I made my own which used the same chunking strategy as the import status loader. Turns out, ureq blocks requests once they hit 10MB by default, which was probably why the gist version didn’t work. But, mine was faster anyways, so I kept it. It’s even cooler since I made it myself. Then, I wired up a couple more commands to let it import straight from archive bytes instead of from a file path, and it finally loaded! Once that worked, I made it pass along the map title, author, and description as well. Then, I extended it to parse out the thumbnail image url and download that, then pass that along to the importer as well. Anyways, that was super fun. I would add a lot more detail, but Stardance limits devlogs to 4000 characters and I’m approaching scarily close to that. I’m planning on just polishing the downloader interface a bit more, and then I’ll do some bugfixes for the next release. See ya!"}, {"post_id": 55430, "ts": "2026-08-27T08:04:57Z", "hours": 3.0236, "text": "rlbuddy now can show people’s avatars cross-platform! It uses a web api I put on Cloudflare Workers which uses platform-specific requests to get avatar urls (see the previous devlog), which the app just fetches and renders. The http part was pretty easy, since I already have wrappers for cached HTTP apis, so I just had to define types for that and hook it up. But, once it was time to display, I ran into a weird problem where egui said there was no image loader installed for jpegs! Even using all_loaders + default didn’t work. Turns out, I have to actually include image as a separate crate and enable all the filetype features I want in there. Once I got it to load, it was time to mess with styling again. egui is really annoying with images, so I spent a while trying to get them to size right first. The default avatar used while they’re loading or for Epic/Switch players is from Fandom, like the rank icons. However, even though the icon is 90x90 pixels, the webp is 128x128 pixels, it just has a bunch of transparent padding. So, I initially messed with egui to do some weird ui allocation and calculation to get it to render right. But, turns out I didn’t have to do all of that. All I had to do was allocate a 28x28 space (the target size), scale that up by the 128/90 ratio, then paint the image onto the new size. Worked great. Then, I got to fight with grid layouts again. For some reason, wrapping the avatar and player details in a horizontal totally exploded all the layouting. So, I ended up making avatar and details separate columns. But, since the “player” header text is a bit larger than the icon width, there was too much padding between the avatar and the player details. So, I did more messing around with egui manual layouting, and ended up allocating a 24x(body text height + 2) space for the header, which would give it the default spacing. Then, I’d copy the Rect, shift the left bound over by 12 pixels, then paint the header onto there. That way, the header is left aligned with the avatar and the avatar + details look like they’re in the same column, even though it’s actually a bunch of stupid manual layout magic behind the scenes."}, {"post_id": 55160, "ts": "2026-08-26T17:10:01Z", "hours": 2.6972, "text": "Made an avatars api for Steam, PSN, and Xbox (Epic doesn’t support profile pictures and I have no clue what goes on in Nintendo-land, even Tracker doesn’t show theirs). It’s on Cloudflare Workers instead of my pi since it’s basically just api integration. I totally overcomplicated finding out how to get Steam profile pictures. Like, it’s insane how easy it actually is. A few weeks ago, I got an old version of Steam Rocket League and decompiled it, trying to look for where it fetched avatars from. After some experimentation and debugging, I learned that when Psyonix builds Rocket League, they give it something that implements OnlineSubsystem for whatever the target platform is. So, Steam gets the Steamworks OnlineSubsystem, Epic gets the EOSOnlineSubsystem, and so forth. And the game doesn’t need to know implementation details, just that it implements OnlineSubsystem, I think the term for that is dynamic dispatch in programming languages. Anyways, once I learned that Rocket League doesn’t do any avatar handling itself, I looked at the Steamworks SDK. Basically, it routes the avatar request through the open Steam client. So after some experimentation, I got it to fetch profile pictures with C++ and the Spacewars test game. But, that obviously wouldn’t work on a server, since the Steam client can’t really run on a Worker. So, I tried to use the Steam Web API. I got an API key, but when I tried, it said I was forbidden. According to the documentation, the GetPlayerSummaries endpoint was at partner.steam-api.com, which was only for publishers. About a week later, so yesterday, I had the genius idea to use api.steampowered.com (the public API domain) instead of partner.steam-api.com. Just swapping them out. And it worked. So, I now had a way to get Steam avatar urls. Xbox and PSN were actually really easy. Xbox has an undocumented API at https://peoplehub-public.xboxlive.com/people/gt(GAMERTAG) which returns json containing the avatar uri, and I used the psn-api package with a new PlayStation account to get PSN profile pictures. Then, I just put all of them in a Worker. So, rlbuddy will be able to show avatars soon enough!"}, {"post_id": 54013, "ts": "2026-08-24T19:11:04Z", "hours": 4.6756, "text": "Recently I was going through game archives and came across the BepInEx tool, which is basically a code injection framework for Unity games. I was really curious as to how it actually launched when the game launched, and found that they use a tool called Unity Doorstop, which adds winhttp.dll to the game’s binary directory. So, when the game tries to use things from winhttp, it goes to the new winhttp.dll instead of the system32 winhttp.dll. From there, the custom dll just forwards all the function calls. However, the custom dll is modified so that on its first interaction, it launches whatever Doorstop is configured to launch, and the mods can be loaded. That’s at least as far as I understood, but it made sense, and I thought, “what if I could launch rlbuddy automatically like this?” So, first, I tried to test it with a custom executable and library. I created a basic executable and DLL in C, and got the executable to load and run some code from the DLL. Then, I created another DLL, and got it to “impersonate” the original DLL. So, I’d rename the first dll to something like “mylibrary_original.dll”, rename the faker dll to “mylibrary.dll”, and the original executable would call the original, but it goes through the faker instead. And it worked! I had successfully created an actual doorstop-like faker. Now it was time to actually try this on Rocket League. So, I did a dumpbin /import on RocketLeague.exe, and found a few good targets - IPHLPAPI.dll, a system dll for networking stuff, and xinput1_3.dll, one shipped with the game for talking to controllers. RocketLeague.exe only imported two functions from each of them, meaning my fake interface wouldn’t have to be too big. So, first I tried to spoof IPHLPAPI.dll. The code was pretty simple, just a couple of global objects to hold the function. So, when the game loaded the DLL, it loaded the fake DLL. Then, when it tried to call some function in the DLL, such as GetAdaptersAddresses, the fake DLL would be the actual receiver, it would log something to a file, and then it would call the REAL GetAdaptersAddresses by dynamically loading the system IPHLPAPI.dll and return it. And… it worked! I opened Rocket League and the log file was created and had the log “GetAdaptersAddresses called!” Then, I launched it with anticheat enabled, and EAC immediately shut it down. I guess that was to be expected, but it was pretty annoying. So, I decided to try one more time, this time with xinput1_3.dll. This was more interesting because it was lazy loaded, and functions were imported by ordinals (2 = XInputGetState, 3 = XInputSetState), so I thought something might be different. So, I modified the original faker to be xinput1_3, replacing the function names and definitions. Since Rocket League imported them by ordinal instead of just the function name, I had to create a .def file as well, and define the ordinals there. Then, I did the same surgery on the binary folder, and after a few small mistake fixes, it worked! But, I still hadn’t tried with EAC enabled. So I launched it with anticheat, and it gave me a different error this time - that it couldn’t determine xinput1_3.dll’s file version. That was a different error! I opened the original xinput1_3.dll’s file properties, and it had copyright, file version, product version, and a couple other random things that were missing in my faker DLL. So I made a .rc file and compiled it together with the faker DLL. Then, most of the properties matched up. So, I tried it, and it seemingly worked! And then EAC said that it couldn’t verify the file again. In the end, I didn’t manage to get it working. I should’ve expected EAC to block me from the beginning, but it was pretty fun, and if I ever want to do something similar with a game without anti-cheat, I can do that now! rlbuddy is pretty closed to being released though, I just want to polish a few things and make sure the docs are fleshed out."}, {"post_id": 51407, "ts": "2026-08-21T02:54:11Z", "hours": 1.5947, "text": "rlbuddy doesn’t use the default eframe persistence feature for savedata anymore, which is pretty cool. There were a few reasons for this: It was kind of unnecessary, since eframe uses ron and it was storing a bunch of extra widget data which didn’t really fit with the app model anyway It took up extra dependencies (removing the feature shaved off like 10) There’s a bug where if the app crashed, it would erase all savedata, which, as you can imagine, was quite annoying especially since I actually use the app along with developing it, meaning if the app crashed in development it would delete all my actual match data. It’s easier to externally modify, so if in development I want to mess with some values it’s a lot easier. Anyways, actually getting the data directory was pretty interesting. I’ve basically decided to only support Windows, since the music player relies on Windows APIs anyways and I don’t think the Stats API even works on Linux. Initially, I checked out the source code for the dirs and directories crates, which led me to the dirs-sys crate (which actually isn’t even on crates.io). It used unsafe raw Win32 SHGetKnownFolderPath with the Roaming folder guid. However, since I was already using the windows-rs crate, I tried to find the WinRT equivalent. I ended up enabling the Storage_Search feature to use the API which would let me get it. Of course, this also didn’t work, because of something to do with how Windows apps are supposed to be registered. So, I ended up just grabbing the APPDATA environment variable and using that, then joining rlbuddy\\data.json to it. It’s pretty underwhelming, but it more than gets the job done. Now, whenever the app panics, I won’t lose all of my settings anymore! Pretty nice. Anyways, that’s what mainly got accomplished. I also tried to set up a GitHub Action to build it and make a release, but I kept hitting errors so I gave up and decided to upload a release next time I get the urge to actually update the GitHub repository. There’s also a few fixes, like the window title which has actually had a typo since refactors from around a month ago, and adding a missing ! which broke toasts when in training. Overall, only an hour or so was tracked, but a heck of a lot got done. See you all next time!"}, {"post_id": 49990, "ts": "2026-08-19T00:44:44Z", "hours": 1.4564, "text": "I added a small toast notification that activates when you score in training, like bakkesmod used to add it to text chat. It also activates when you hit the crossbar so you can feel even more remorseful that your beautiful musty didn’t go in :) I actually got the program to segfault while making it. egui’s external viewport api is kinda funky… there are two modes, immediate and deferred, and immediate is supposed to be easier to use. However, when I tried to use immediate mode, it panicked saying that the egui backend was implemented incorrectly. So I switched to deferred, and it gave me the segfault. There were 3 things I had to do: I stored all active toasts in a Vec, and render them in the external viewport every frame. When one should disappear, it uses Vec::retain to remove old ones. However, this doesn’t do any cleanup, meaning the viewport would go bonkers and crash the application. So, I had to close the viewport first. Calling ui.ctx() from the viewport made a segfault, probably my fault but I couldn’t figure out how to resolve it so I ended up just sending Viewport::Close from the main loop. Even if I sent Viewport::Close from the main loop, it still gave the weird error. So, I resolved it by adding a small delay from after it should close, which is when it sends the Viewport::Close command, and when it’s actually removed from the Vec of toasts. Well, I did all that, and it was working, but there was a really weird bug where it would lag the whole system while opening/closing. So, I switched from immediate to deferred viewport mode, and it stopped happening! I hit my first powerful airdribble musty with it on, it was around 105kph, and a total fluke. I wish I had gotten it on recording."}, {"post_id": 49200, "ts": "2026-08-17T21:06:45Z", "hours": 9.2278, "text": "I replaced the Spotify integration with a generic one that uses the Windows GSMTC api, meaning it’s way snappier, since it’s not making network requests every time it wants new data! The only reason it needs data is to fetch playback info, and it’s easy to do that since the windows crate gives a bunch of event listeners, meaning I also don’t need to poll anymore. Unfortunately, the integration from browsers etc. is kind of buggy so thumbnail loading is relatively inconsistent, but by adding some caching and timeouts it works flawlessly :) I also redid the UI since the old one was frankly quite ugly. Since the playback listener doesn’t update that frequently for progressbar changes, I also made it fake progress when music is playing and the ui updates, that way even if the latest data is stale, it still looks like the progress bar is moving. I’m now working on better session stats, I’ll probably end up replacing the all-time stat tracker with more in-depth session-only ones"}, {"post_id": 46652, "ts": "2026-08-14T02:03:01Z", "hours": 4.1664, "text": "rlbuddy finally has an MMR graph! I’m planning on adding more stat displays soon, like W/L, goals for/against, saves, etc. but this is a really big milestone since it’s a feature that a lot of players actually use (I basically only play casual so it’s not really for me 😅). Anyways, this also required me to rearchitect how matches are stored, and now there are two representations: a session match, which contains lots of information about it like detailed player skill information, and a stripped match, which is minimal and stores very little because it actually gets saved to disk! So, match history also gets saved now! This also opens up the doorway to a Deja-Vu-like thing which tells you if you’ve played with someone before and when! It uses the egui_plot library to show the graph. Funnily enough, basically every default option for a displayed plot was the opposite for this, and so the plot builder ended up using almost every method. Also, once again, hackatime is wrong because Zed likes to uninstall extensions randomly :("}, {"post_id": 42417, "ts": "2026-08-07T03:32:05Z", "hours": 2.1161, "text": "The Stats API finally provides the actual playlist id, so rlbuddy isn’t limited to guessing between 1v1/2v2/3v3/4v4 anymore! This brings in some cool extra features like displaying the rank each player is in for the given playlist as well as even better Discord presence! I looked through BakkesMod as well as some psynet endpoints to grab the list of playlist ids to associate with each actual playlist. There isn’t really an image to go with this, so here’s the monster enum of playlists :)"}, {"post_id": 40468, "ts": "2026-08-04T00:47:59Z", "hours": 2.2011, "text": "I rewrote the API admin page with Svelte! Before, it was a quick-and-dirty raw html/css/js solution, but now it should be a lot less annoying to work on. I improved authorization a lot, now instead of adding the pw query parameter for every request you put in a password once and it actually uses the Authorization header. Unfortunately, the browser EventSource doesn’t support headers except for in cookies, meaning I had to use the custom eventsource library for bootstrapping since the bootstrap API uses SSE. There’s basically a whole Vite project in src/webadmin, with its own tsconfig.json and everything. Because of the way it’s set up, development needs Vite running in build watch mode which builds to where the main app serves it out of. It makes deployment a lot easier, though. I didn’t really add any actual features to it, except for a log filter. However, it should be way easier to add more features in the future, like a api tester!"}, {"post_id": 38450, "ts": "2026-07-31T20:22:49Z", "hours": 6.755, "text": "rlbuddy has player lookup now, so you can check for smurfs without needing to boot up a heavy browser while playing. Unfortunately, this has to get its information from TRN because it’s the only source of this information, the rocket league apis don’t provide it. There’s some cool refactors behind the scenes, though. I generalized almost all HTTP requests into its own struct, so it removed like 200 lines of boilerplate code into a single generic module. It automatically handles caching, using new threads, ignoring if there’s already an inflight request, loading states, and automatic serde_json deserialization. The Epic id, uncensorer, ranks, and new TRN apis all use it now! Also, the button to open in TRN from the player list opens the player info dialog instead (though if you need to, you can open in TRN from there)."}, {"post_id": 34762, "ts": "2026-07-25T19:02:58Z", "hours": 1.9536, "text": "Note: I’ve actually had way more hours than it says, but I reinstalled windows and forgot to set up hackatime again so all that’s gone :( mmr-api-v2 finally has an admin page, so I don’t have to ssh into my pi just to check some debug information anymore! It is kind of developer-oriented but I think it looks and works well enough for what it is for. In the actual rlbuddy app, there are some cool new features! Facilitated by lots of refactors, of course (turns out, the matches service was bigger than the actual rocket league service, so I finally separated them). Spotify integration now has a previous button as well as a skip-to-song-in-current-playlist button. Match history was a feature that I’ve kind of ignored, so I gave it a much needed touch-up so its easier to view and browse. A pretty cool thing is that that’s been added a rocket-league-playerid-to-epic-games-id translation endpoint. This means that you can see Switch players’ TRN profiles through their linked Epic account, since TRN doesn’t let you check based on Switch name alone. Finally, there are some small quality of life improvements, like less deadlocks, better anthem pausing, and the open panel list persisting."}, {"post_id": 30390, "ts": "2026-07-17T21:27:26Z", "hours": 3.8292, "text": "Turns out, for spotify integration, I can’t actually let other people use my spotify development mode app. It’s limited to 5 users which I have to invite by email. And to get a quota extension, I need 250,000 existing monthy active users, which I’m slightly short of (by about 249,999). So, I changed the login flow so that each user has to make their own Spotify app, and log in using the client ID from there. I didn’t change the current PKCE flow since that didn’t really need to be changed, but setup unfortunately takes a little more work now. Also, it has transparency now."}, {"post_id": 28659, "ts": "2026-07-15T01:29:51Z", "hours": 13.4597, "text": "It’s been a week since the last devlog, and lots has changed! 1. new architecture! The code was getting pretty messy since I had kept adding new features without really doing major refactors. So, I finally took it upon myself to actually organize everything and come up with a proper architecture. Each feature has its own module, containing services and widgets. Services have all the state, for example the Spotify service stores the current song and settings and the Rocket League module exposes a Matches service for the current match and past matches, which the match and past match widgets actually show. Services all have update functions which get called every 10ms. There are two ways for services to talk to the rest of the app: events and state. State is straightforward, it’s usually just an Rc with a custom generic struct I made to manage whether the widget can read/write to it. Events are enums which the update method returns, and can get passed to other update methods. For example, the Stats API service doesn’t have its own widget, it only connects to the stats api and returns match events which other features can use, like Spotify using replay start/end events to play/pause music. 2. new ui! The dashboard part isn’t as ugly as it was before, and has actual cards for every panel instead of just having horizontal lines in between. Even though it sounds pretty small, it’s actually huge for the prettiness of the app! Along with this, the behaviour of managing panels also changed. There’s an actual close button, and instead of having one button per panel at the bottom, there’s a small picker to open/close them. Also, you can also move them up and down now 3. Automatic stats api setup This is a pretty small feature. It lets you select the path to the rocket league executable, detects whether it’s already enabled, and turns it on if it’s disabled. It does this by just replacing a line in the stats api configuration. 4. mmr api v2 doesnt explode There were a few bugs which would make the API disconnect randomly, but they’ve been ironed out. This also made it so the app doesn’t have to deal with failure behaviour and can just keep asking until the API responds."}, {"post_id": 23975, "ts": "2026-07-07T06:51:05Z", "hours": 8.1119, "text": "rlbuddy now has a little bit of Spotify support! Although it looks like a just few elements and a button, theres a lot going on behind the scenes. I’m not embedding an entire webview to run the web playback SDK this time, so it just uses the Web API to keep resource usage nice and low. Since it’s a native app, it has to jump through some hurdles to authorize, using the PKCE extension version since the client secret can’t be stored securely. It uses the webbrowser crate to open the Spotify auth page, and starts a tiny little oneshot HTTP server on a raw TCPListener. The redirect is set to the HTTP server, and then as soon as it receives the request, it sends a simple HTML page and continues. From there, it receives the authorization code and can continue as normal :) Other than that, it’s just normal REST api interaction. I did learn a ton about thread safety (I hit so many deadlocks), because unlike the stats api, for example, which just sits in a different thread and hurls events over a channel, it actually has to work with the user. It’s also able to pause during goal replays, and that was pretty annoying to structure. Eventually, I decided to just use a channel where the stats api could send replay start/end events through the match widget. Looking back on it, the solution was extremely simple 😅 I am planning to add a bit more, like shuffle/repeat controls as well as a previous track button, but this feature turned out pretty nicely!"}, {"post_id": 22551, "ts": "2026-07-05T02:19:55Z", "hours": 3.7678, "text": "rlbuddy can now show Rich Presence in Discord, so now all your friends can see you destroying your opponents ;)"}, {"post_id": 21165, "ts": "2026-07-02T23:37:22Z", "hours": 12.7792, "text": "rlbuddy has had major ui overhauls to shift it more towards an actual overlay rather than just an app that displays ranks. there are new features like a settings menu as well as major architectural overhauls, with more coming soon!"}, {"post_id": 16053, "ts": "2026-06-26T02:45:17Z", "hours": 2.3483, "text": "Got it working as an actual overlay!! Right now it just displays ranks, though"}, {"post_id": 15855, "ts": "2026-06-25T19:39:34Z", "hours": 30.4556, "text": "I got a stable MMR/ranks api working!! https://github.com/Kalilamodow/mmr-api-v2 Basically, it’s an http server that opens a websocket to the actual rocket league game api in the background and spoofs a real client connection. The socket only stays active for 5 seconds at a time though so I don’t kill psyonix’s servers. That way, rlbuddy can get ranks without getting blocked by tracker network! I originally tried to do it on Cloudflare, but the workers outbound websocket didn’t work since you can’t send custom headers with them. I ended up having to use my old raspberry pi and set up a homeserver behind Cloudflare tunnels, and I’m actually planning on moving the rest of my stuff that used to be on a VPS onto the pi."}]...
|
Title: rlbuddy by @kmdw | Stardance
Devlogs:
I go Title: rlbuddy by @kmdw | Stardance
Devlogs:
I got a stable MMR/ranks api working!! https://github.com/Kalilamodow/mmr-api-v2 Basically, it’s an http server that opens a websocket to the actual rocket league game api in the background and spoofs a real client connection. The socket only stays active for 5 seconds at a time though so I don’t kill psyonix’s servers. That way, rlbuddy can get ranks without getting blocked by tracker network! I originally tried to do it on Cloudflare, but the workers outbound websocket didn’t work since you can’t send custom headers with them. I ended up having to use my old raspberry pi and set up a homeserver behind Cloudflare tunnels, and I’m actually planning on moving the rest of my stuff that used to be on a VPS onto the pi.
Got it working as an actual overlay!! Right now it just displays ranks, though
rlbuddy has had major ui overhauls to shift it more towards an actual overlay rather than just an app that displays ranks. there are new features like a settings menu as well as major architectural overhauls, with more coming soon!
rlbuddy can now show Rich Presence in Discord, so now all your friends can see you destroying your opponents ;)
rlbuddy now has a little bit of Spotify support! Although it looks like a just few elements and a button, theres a lot going on behind the scenes. I’m not embedding an entire webview to run the web playback SDK this time, so it just uses the Web API to keep resource usage nice and low. Since it’s a native app, it has to jump through some hurdles to authorize, using the PKCE extension version since the client secret can’t be stored securely. It uses the webbrowser crate to open the Spotify auth page, and starts a tiny little oneshot HTTP server on a raw TCPListener. The redirect is set to the HTTP server, and then as soon as it receives the request, it sends a simple HTML page and continues. From there, it receives the authorization code and can continue as normal :) Other than that, it’s just normal REST api interaction. I did learn a ton about thread safety (I hit so many deadlocks), because unlike the stats api, for example, which just sits in a different thread and hurls events over a channel, it actually has to work with the user. It’s also able to pause during goal replays, and that was pretty annoying to structure. Eventually, I decided to just use a channel where the stats api could send replay start/end events through the match widget. Looking back on it, the solution was extremely simple 😅 I am planning to add a bit more, like shuffle/repeat controls as well as a previous track button, but this feature turned out pretty nicely!
It’s been a week since the last devlog, and lots has changed! 1. new architecture! The code was getting pretty messy since I had kept adding new features without really doing major refactors. So, I finally took it upon myself to actually organize everything and come up with a proper architecture. Each feature has its own module, containing services and widgets. Services have all the state, for example the Spotify service stores the current song and settings and the Rocket League module exposes a Matches service for the current match and past matches, which the match and past match widgets actually show. Services all have update functions which get called every 10ms. There are two ways for services to talk to the rest of the app: events and state. State is straightforward, it’s usually just an Rc with a custom generic struct I made to manage whether the widget can read/write to it. Events are enums which the update method returns, and can get passed to other update methods. For example, the Stats API service doesn’t have its own widget, it only connects to the stats api and returns match events which other features can use, like Spotify using replay start/end events to play/pause music. 2. new ui! The dashboard part isn’t as ugly as it was before, and has actual cards for every panel instead of just having horizontal lines in between. Even though it sounds pretty small, it’s actually huge for the prettiness of the app! Along with this, the behaviour of managing panels also changed. There’s an actual close button, and instead of having one button per panel at the bottom, there’s a small picker to open/close them. Also, you can also move them up and down now 3. Automatic stats api setup This is a pretty small feature. It lets you select the path to the rocket league executable, detects whether it’s already enabled, and turns it on if it’s disabled. It does this by just replacing a line in the stats api configuration. 4. mmr api v2 doesnt explode There were a few bugs which would make the API disconnect randomly, but they’ve been ironed out. This also made it so the app doesn’t have to deal with failure behaviour and can just keep asking until the API responds.
Turns out, for spotify integration, I can’t actually let other people use my spotify development mode app. It’s limited to 5 users which I have to invite by email. And to get a quota extension, I need 250,000 existing monthy active users, which I’m slightly short of (by about 249,999). So, I changed the login flow so that each user has to make their own Spotify app, and log in using the client ID from there. I didn’t change the current PKCE flow since that didn’t really need to be changed, but setup unfortunately takes a little more work now. Also, it has transparency now.
Note: I’ve actually had way more hours than it says, but I reinstalled windows and forgot to set up hackatime again so all that’s gone :( mmr-api-v2 finally has an admin page, so I don’t have to ssh into my pi just to check some debug information anymore! It is kind of developer-oriented but I think it looks and works well enough for what it is for. In the actual rlbuddy app, there are some cool new features! Facilitated by lots of refactors, of course (turns out, the matches service was bigger than the actual rocket league service, so I finally separated them). Spotify integration now has a previous button as well as a skip-to-song-in-current-playlist button. Match history was a feature that I’ve kind of ignored, so I gave it a much needed touch-up so its easier to view and browse. A pretty cool thing is that that’s been added a rocket-league-playerid-to-epic-games-id translation endpoint. This means that you can see Switch players’ TRN profiles through their linked Epic account, since TRN doesn’t let you check based on Switch name alone. Finally, there are some small quality of life improvements, like less deadlocks, better anthem pausing, and the open panel list persisting.
rlbuddy has player lookup now, so you can check for smurfs without needing to boot up a heavy browser while playing. Unfortunately, this has to get its information from TRN because it’s the only source of this information, the rocket league apis don’t provide it. There’s some cool refactors behind the scenes, though. I generalized almost all HTTP requests into its own struct, so it removed like 200 lines of boilerplate code into a single generic module. It automatically handles caching, using new threads, ignoring if there’s already an inflight request, loading states, and automatic serde_json deserialization. The Epic id, uncensorer, ranks, and new TRN apis all use it now! Also, the button to open in TRN from the player list opens the player info dialog instead (though if you need to, you can open in TRN from there).
I rewrote the API admin page with Svelte! Before, it was a quick-and-dirty raw html/css/js solution, but now it should be a lot less annoying to work on. I improved authorization a lot, now instead of adding the pw query parameter for every request you put in a password once and it actually uses the Authorization header. Unfortunately, the browser EventSource doesn’t support headers except for in cookies, meaning I had to use the custom eventsource library for bootstrapping since the bootstrap API uses SSE. There’s basically a whole Vite project in src/webadmin, with its own tsconfig.json and everything. Because of the way it’s set up, development needs Vite running in build watch mode which builds to where the main app serves it out of. It makes deployment a lot easier, though. I didn’t really add any actual features to it, except for a log filter. However, it should be way easier to add more features in the future, like a api tester!
The Stats API finally provides the actual playlist id, so rlbuddy isn’t limited to guessing between 1v1/2v2/3v3/4v4 anymore! This brings in some cool extra features like displaying the rank each player is in for the given playlist as well as even better Discord presence! I looked through BakkesMod as well as some psynet endpoints to grab the list of playlist ids to associate with each actual playlist. There isn’t really an image to go with this, so here’s the monster enum of playlists :)
rlbuddy finally has an MMR graph! I’m planning on adding more stat displays soon, like W/L, goals for/against, saves, etc. but this is a really big milestone since it’s a feature that a lot of players actually use (I basically only play casual so it’s not really for me 😅). Anyways, this also required me to rearchitect how matches are stored, and now there are two representations: a session match, which contains lots of information about it like detailed player skill information, and a stripped match, which is minimal and stores very little because it actually gets saved to disk! So, match history also gets saved now! This also opens up the doorway to a Deja-Vu-like thing which tells you if you’ve played with someone before and when! It uses the egui_plot library to show the graph. Funnily enough, basically every default option for a displayed plot was the opposite for this, and so the plot builder ended up using almost every method. Also, once again, hackatime is wrong because Zed likes to uninstall extensions randomly :(
I replaced the Spotify integration with a generic one that uses the Windows GSMTC api, meaning it’s way snappier, since it’s not making network requests every time it wants new data! The only reason it needs data is to fetch playback info, and it’s easy to do that since the windows crate gives a bunch of event listeners, meaning I also don’t need to poll anymore. Unfortunately, the integration from browsers etc. is kind of buggy so thumbnail loading is relatively inconsistent, but by adding some caching and timeouts it works flawlessly :) I also redid the UI since the old one was frankly quite ugly. Since the playback listener doesn’t update that frequently for progressbar changes, I also made it fake progress when music is playing and the ui updates, that way even if the latest data is stale, it still looks like the progress bar is moving. I’m now working on better session stats, I’ll probably end up replacing the all-time stat tracker with more in-depth session-only ones
I added a small toast notification that activates when you score in training, like bakkesmod used to add it to text chat. It also activates when you hit the crossbar so you can feel even more remorseful that your beautiful musty didn’t go in :) I actually got the program to segfault while making it. egui’s external viewport api is kinda funky… there are two modes, immediate and deferred, and immediate is supposed to be easier to use. However, when I tried to use immediate mode, it panicked saying that the egui backend was implemented incorrectly. So I switched to deferred, and it gave me the segfault. There were 3 things I had to do: I stored all active toasts in a Vec, and render them in the external viewport every frame. When one should disappear, it uses Vec::retain to remove old ones. However, this doesn’t do any cleanup, meaning the viewport would go bonkers and crash the application. So, I had to close the viewport first. Calling ui.ctx() from the viewport made a segfault, probably my fault but I couldn’t figure out how to resolve it so I ended up just sending Viewport::Close from the main loop. Even if I sent Viewport::Close from the main loop, it still gave the weird error. So, I resolved it by adding a small delay from after it should close, which is when it sends the Viewport::Close command, and when it’s actually removed from the Vec of toasts. Well, I did all that, and it was working, but there was a really weird bug where it would lag the whole system while opening/closing. So, I switched from immediate to deferred viewport mode, and it stopped happening! I hit my first powerful airdribble musty with it on, it was around 105kph, and a total fluke. I wish I had gotten it on recording.
rlbuddy doesn’t use the default eframe persistence feature for savedata anymore, which is pretty cool. There were a few reasons for this: It was kind of unnecessary, since eframe uses ron and it was storing a bunch of extra widget data which didn’t really fit with the app model anyway It took up extra dependencies (removing the feature shaved off like 10) There’s a bug where if the app crashed, it would erase all savedata, which, as you can imagine, was quite annoying especially since I actually use the app along with developing it, meaning if the app crashed in development it would delete all my actual match data. It’s easier to externally modify, so if in development I want to mess with some values it’s a lot easier. Anyways, actually getting the data directory was pretty interesting. I’ve basically decided to only support Windows, since the music player relies on Windows APIs anyways and I don’t think the Stats API even works on Linux. Initially, I checked out the source code for the dirs and directories crates, which led me to the dirs-sys crate (which actually isn’t even on crates.io). It used unsafe raw Win32 SHGetKnownFolderPath with the Roaming folder guid. However, since I was already using the windows-rs crate, I tried to find the WinRT equivalent. I ended up enabling the Storage_Search feature to use the API which would let me get it. Of course, this also didn’t work, because of something to do with how Windows apps are supposed to be registered. So, I ended up just grabbing the APPDATA environment variable and using that, then joining rlbuddy\data.json to it. It’s pretty underwhelming, but it more than gets the job done. Now, whenever the app panics, I won’t lose all of my settings anymore! Pretty nice. Anyways, that’s what mainly got accomplished. I also tried to set up a GitHub Action to build it and make a release, but I kept hitting errors so I gave up and decided to upload a release next time I get the urge to actually update the GitHub repository. There’s also a few fixes, like the window title which has actually had a typo since refactors from around a month ago, and adding a missing ! which broke toasts when in training. Overall, only an hour or so was tracked, but a heck of a lot got done. See you all next time!
Recently I was going through game archives and came across the BepInEx tool, which is basically a code injection framework for Unity games. I was really curious as to how it actually launched when the game launched, and found that they use a tool called Unity Doorstop, which adds winhttp.dll to the game’s binary directory. So, when the game tries to use things from winhttp, it goes to the new winhttp.dll instead of the system32 winhttp.dll. From there, the custom dll just forwards all the function calls. However, the custom dll is modified so that on its first interaction, it launches whatever Doorstop is configured to launch, and the mods can be loaded. That’s at least as far as I understood, but it made sense, and I thought, “what if I could launch rlbuddy automatically like this?” So, first, I tried to test it with a custom executable and library. I created a basic executable and DLL in C, and got the executable to load and run some code from the DLL. Then, I created another DLL, and got it to “impersonate” the original DLL. So, I’d rename the first dll to something like “mylibrary_original.dll”, rename the faker dll to “mylibrary.dll”, and the original executable would call the original, but it goes through the faker instead. And it worked! I had successfully created an actual doorstop-like faker. Now it was time to actually try this on Rocket League. So, I did a dumpbin /import on RocketLeague.exe, and found a few good targets - IPHLPAPI.dll, a system dll for networking stuff, and xinput1_3.dll, one shipped with the game for talking to controllers. RocketLeague.exe only imported two functions from each of them, meaning my fake interface wouldn’t have to be too big. So, first I tried to spoof IPHLPAPI.dll. The code was pretty simple, just a couple of global objects to hold the function. So, when the game loaded the DLL, it loaded the fake DLL. Then, when it tried to call some function in the DLL, such as GetAdaptersAddresses, the fake DLL would be the actual receiver, it would log something to a file, and then it would call the REAL GetAdaptersAddresses by dynamically loading the system IPHLPAPI.dll and return it. And… it worked! I opened Rocket League and the log file was created and had the log “GetAdaptersAddresses called!” Then, I launched it with anticheat enabled, and EAC immediately shut it down. I guess that was to be expected, but it was pretty annoying. So, I decided to try one more time, this time with xinput1_3.dll. This was more interesting because it was lazy loaded, and functions were imported by ordinals (2 = XInputGetState, 3 = XInputSetState), so I thought something might be different. So, I modified the original faker to be xinput1_3, replacing the function names and definitions. Since Rocket League imported them by ordinal instead of just the function name, I had to create a .def file as well, and define the ordinals there. Then, I did the same surgery on the binary folder, and after a few small mistake fixes, it worked! But, I still hadn’t tried with EAC enabled. So I launched it with anticheat, and it gave me a different error this time - that it couldn’t determine xinput1_3.dll’s file version. That was a different error! I opened the original xinput1_3.dll’s file properties, and it had copyright, file version, product version, and a couple other random things that were missing in my faker DLL. So I made a .rc file and compiled it together with the faker DLL. Then, most of the properties matched up. So, I tried it, and it seemingly worked! And then EAC said that it couldn’t verify the file again. In the end, I didn’t manage to get it working. I should’ve expected EAC to block me from the beginning, but it was pretty fun, and if I ever want to do something similar with a game without anti-cheat, I can do that now! rlbuddy is pretty closed to being released though, I just want to polish a few things and make sure the docs are fleshed out.
Made an avatars api for Steam, PSN, and Xbox (Epic doesn’t support profile pictures and I have no clue what goes on in Nintendo-land, even Tracker doesn’t show theirs). It’s on Cloudflare Workers instead of my pi since it’s basically just api integration. I totally overcomplicated finding out how to get Steam profile pictures. Like, it’s insane how easy it actually is. A few weeks ago, I got an old version of Steam Rocket League and decompiled it, trying to look for where it fetched avatars from. After some experimentation and debugging, I learned that when Psyonix builds Rocket League, they give it something that implements OnlineSubsystem for whatever the target platform is. So, Steam gets the Steamworks OnlineSubsystem, Epic gets the EOSOnlineSubsystem, and so forth. And the game doesn’t need to know implementation details, just that it implements OnlineSubsystem, I think the term for that is dynamic dispatch in programming languages. Anyways, once I learned that Rocket League doesn’t do any avatar handling itself, I looked at the Steamworks SDK. Basically, it routes the avatar request through the open Steam client. So after some experimentation, I got it to fetch profile pictures with C++ and the Spacewars test game. But, that obviously wouldn’t work on a server, since the Steam client can’t really run on a Worker. So, I tried to use the Steam Web API. I got an API key, but when I tried, it said I was forbidden. According to the documentation, the GetPlayerSummaries endpoint was at partner.steam-api.com, which was only for publishers. About a week later, so yesterday, I had the genius idea to use api.steampowered.com (the public API domain) instead of partner.steam-api.com. Just swapping them out. And it worked. So, I now had a way to get Steam avatar urls. Xbox and PSN were actually really easy. Xbox has an undocumented API at https://peoplehub-public.xboxlive.com/people/gt(GAMERTAG) which returns json containing the avatar uri, and I used the psn-api package with a new PlayStation account to get PSN profile pictures. Then, I just put all of them in a Worker. So, rlbuddy will be able to show avatars soon enough!
rlbuddy now can show people’s avatars cross-platform! It uses a web api I put on Cloudflare Workers which uses platform-specific requests to get avatar urls (see the previous devlog), which the app just fetches and renders. The http part was pretty easy, since I already have wrappers for cached HTTP apis, so I just had to define types for that and hook it up. But, once it was time to display, I ran into a weird problem where egui said there was no image loader installed for jpegs! Even using all_loaders + default didn’t work. Turns out, I have to actually include image as a separate crate and enable all the filetype features I want in there. Once I got it to load, it was time to mess with styling again. egui is really annoying with images, so I spent a while trying to get them to size right first. The default avatar used while they’re loading or for Epic/Switch players is from Fandom, like the rank icons. However, even though the icon is 90x90 pixels, the webp is 128x128 pixels, it just has a bunch of transparent padding. So, I initially messed with egui to do some weird ui allocation and calculation to get it to render right. But, turns out I didn’t have to do all of that. All I had to do was allocate a 28x28 space (the target size), scale that up by the 128/90 ratio, then paint the image onto the new size. Worked great. Then, I got to fight with grid layouts again. For some reason, wrapping the avatar and player details in a horizontal totally exploded all the layouting. So, I ended up making avatar and details separate columns. But, since the “player” header text is a bit larger than the icon width, there was too much padding between the avatar and the player details. So, I did more messing around with egui manual layouting, and ended up allocating a 24x(body text height + 2) space for the header, which would give it the default spacing. Then, I’d copy the Rect, shift the left bound over by 12 pixels, then paint the header onto there. That way, the header is left aligned with the avatar and the avatar + details look like they’re in the same column, even though it’s actually a bunch of stupid manual layout magic behind the scenes.
rlbuddy can load custom maps now! Either you can download them and then import from a zip, or you can download them from bakkesplugins straight from the app! Importing it from a file is relatively easy. I checked the code for Lethamyr’s custom map loader to see what exactly it did, and for Lethamyr maps it: extracted the zip into a temp dir read info.json, .udk, and preview.jpg copied those into its own saved map directory I knew Rust, being a low-level language would let me not have to extract the zip fully first. Additionally, Bakkesplugins maps don’t contain info.json or preview.jpg, they only contain a udk file, so I had to make sure it would work with that. So, the import code I wrote basically: enumerates files in the zip extracts info.json (if found) into a struct copies *.upk into the target directory copies *.jpg into the target directory/preview.jpg if it exists It worked great. I made a really cool looking widget with stupid manual layouting behind it for map selection. First, it would add the preview image (or a black background if none). Then, it would get the screen rect of the image, and draw a semitransparent black background onto it (for contrast). Finally, it drew the actual contents in that screen rect. So, I got pretty-looking tiles with preview image backgrounds! I also added a progress bar for import status. I benchmarked each main part of the import process and found that decompressing/extracting the upk file took around 2 seconds, for which the ui thread would be totally frozen (big nono)! So, I moved the work onto another thread, and also made it decompress/load in 100 total chunks, updating a progress bar each time. I then moved on to the map downloader. Bakkesplugins doesn’t have an API, so it relies on scraping the website. I didn’t actually use any external crates for gathering information, though. I wrote a parser which takes a list of rules, each with a start and end search string, and looks for each of them in sequence till it fails to find a rule. I tried to make it completely zero-copy, but something it needed was to turn an [Option; N] into a Option<[T; N]> if every element was Some , but the only way I could figure out how to do that was with an iterator. It’s still really fast though. So, it parses search results, including map titles and urls straight out of the html string without needing an HTML parser or regex library. Once it was able to load results from the search page, I went to sleep and came back today. Now I had to get it to actually download the ZIP files and preview images, and import them straight into the app. It first has to download/parse the plugin info page since the ZIP url isn’t available in the search results. Then, it requests the ZIP. I had some trouble with this, since I wanted to show a progress bar for the download status. I first tried a function from this gist , but it didn’t work. So, I made my own which used the same chunking strategy as the import status loader. Turns out, ureq blocks requests once they hit 10MB by default, which was probably why the gist version didn’t work. But, mine was faster anyways, so I kept it. It’s even cooler since I made it myself. Then, I wired up a couple more commands to let it import straight from archive bytes instead of from a file path, and it finally loaded! Once that worked, I made it pass along the map title, author, and description as well. Then, I extended it to parse out the thumbnail image url and download that, then pass that along to the importer as well. Anyways, that was super fun. I would add a lot more detail, but Stardance limits devlogs to 4000 characters and I’m approaching scarily close to that. I’m planning on just polishing the downloader interface a bit more, and then I’ll do some bugfixes for the next release. See ya!
I moved the mmr server to alpine linux! Before, it was regular raspberry pi os lite on the Pi. But, it was behaving strangely, randomly rebooting and things, so I decided to try installing Alpine. The lightweight distro has been on my radar for a while now, but this was my first time ever actually trying it. I imaged it normally with the raspberry pi imager, and put it on the pi, did setup-alpine, and all was well. But, I put in the wrong wifi password at first, which broke everything else. I tried changing the wifi config and doing something with wpa_supplicant (thanks google ai overview), but nothing fixed it so I ended up doing a fresh install. This time, I put in the right password and everything went great. I set up ssh, node, pm2, etc. and moved the server files onto the pi through sftp. Then, I got it running over my local network and set up authentication. So, it was time to set up the Cloudflare tunnel again. I followed this neat guide , changing values as needed (for example, my pi is aarch64, but the guide was x64, so I spent a while figuring out why the pi kept giving elf errors). It took a really long time to set up the openrc startup script because 1. cloudflare rotates the setup token relatively frequently and 2. I accidentally put 2 –token arguments. And since I was using vi, I didn’t even notice and kept trying to figure out why cloudflared crashed on startup. But I eventually found out and removed the duplicate and it worked! Then I finished setting up pm2 and put the pi in its home next to the router, and mmr.kmdw.dev is back up exposing your lobby’s skills. See you in the next one! Yes I know part of the token is in the image but it’s used already so it doesn’t matter
rlbuddy has a gamepad overlay now so that you can show off your awesome bindings! Don’t even bother figuring out mine. It’s kinda starting to become hellish to add new widgets/services, but I’m almost done for the next ship so I doubt I’ll refactor just yet [edit: im doing a super refactor now hehe]. I made the overlay with the raw egui::Painter rather than actual controls (since it would be pretty hard to make a controller out of Buttons or something). I also prefer to avoid embedding static assets, otherwise I might have made an actual one in MS Paint or something. I also can’t seem to find a good egui version. I’ve been on 0.34 almost the whole time, which works well but doesn’t support transparency in extra viewports. But, 0.35/0.36 broke external viewports. So, until then, we get an opaque gamepad overlay :( but the main app is still transparent, so it’s not horrible. Rolled an 8 and a 9 on the Stardance rng today! I’m so proud of myself.
I’m MASSIVELY refactoring rlbuddy! This, I think is the second (and hopefully the final) major refactor to the codebase. So, basically the current architecture is split into services and widgets. Each service has an update method, which does its stuff (draining subscribers, processing commands, etc). Since egui is immediate mode, this architecture works unlike callbacks/event-based which I actually tried to implement but couldn’t. Every widget is just implements egui::Widget, that holds some state handles (Rc) and command senders (Sender), usually just one for its respective service. Services are alive for the lifetime of the app, while widgets are the openable panels you see. Widgets display the data from service state, and talk back to it by mutating shared state or sending commands. I think music control has been the best for me to wrap my head around to decide about changes, so I’ll explain it with that. Music control had a controller, a service, and a widget file. The controller was a thread pool manager (if you can call the questionable implementation that) around Windows’ GSMTC API to give a single-thread api for it (since the app is basically single-threaded). Then, the service is always alive, syncing a state struct between the controller and its own state handle. Finally, the widget is optionally there as a panel, with next/previous/pause buttons which send commands and the ui which reads service state to display. It also has a mutable service state reference for settings (the “pause during anthems” button). The god object RlbuddyApp struct stores all of this stuff. So it has a music_control_service, music_control_widget, stats_api_service, all of those fields, which kinda sucked. On top of that, I had to maintain an enum of every panel, which was enumerated for the “open panel” popup and to render each one if it’s open. you might think “hey… isn’t that a self referential struct? how does music_control_widget store a reference to music_control_service?” Well, music_control_widget internally only has fields for the music_control_service’s state handles which are Rc/Arcs. So it only needs a reference to the service for it’s Widget::new function lifetime, all is well. Although it doesn’t look horrible, it was HELL to do anything. For example, refactoring the gilrs service out of hotkey and adding the gamepad overlay probably involved just as much fiddling around with Rcs in RlbuddyApp as adding the actual feature. So, I decided to move to a new architecture: services + features! And, after thinking about it for around 8 hours (i slept), I decided on something a bit different. Services + panels! Yeah, basically the same as services + widgets, but I was gonna implement it GOOD this time, and make design decisions that wouldn’t bite me in the ass later on. I wrote my first traits - Service, which requires update and optionally save , and Panel , which requires name() -> &'static str and ui (same as egui::Widget: :ui). Then, I’d change how components are registered: instead of keeping every service and widget as a field in RlBuddyApp, I’d make a Vec<Box<dyn Service/Panel» for each, and loop through it. I spent about an hour trying to get Panels to automatically implement ui by requiring egui::Widget as a supertrait, which doesn’t work since widgets are stateful (need &self). So, I tried to require &mut Self to implement egui::Widget, but apparently something to do with type erasure doesn’t let that work with dynamic dispatch which was needed for the Vec<Box> thing. I’ve moved a few features to the new architecture, but I’m still in the process of finishing up. Additionally, it has to work side-by-side with existing crappy monster-Panel-enum architecture, which sucks to maintain. Okay, once again, I had to strip down a ton of detail (and my terrible wording but wtv) cause I hit like 5000 characters b4 realizing. See ya! May the Stardance gods increase devlog limits :)
I finished the big refactor! I pushed around 25 commits to GitHub at once. I’m really gonna enjoy adding the next feature, basking in how much easier it is ;) Apparently, I never made a service for session stats, so it currently can’t persist its one setting. Actually, writing this I realized that the setting isn’t actually used, so I can delete it. That’s great. After finally kicking out all the annoying legacy panel code, I remembered that I never re-implemented the positioning or its related persistence code for the new panel system. The persistence system was reworked too, something that I don’t think really got included in the previous devlog. Each service can implement save , and there are generic helper methods in a common module for saving/loading serde structs to a file in the app data directory. The actual app now uses this method as well instead of doing all the pathing logic itself, so the persistence module is pretty small and could honestly be moved into app.rs. For new positioning, I store a list of open panels. Weirdly enough, it’s actually a lot easier than adding a position/is_open field to every AppPanel. So, I run the name through a Hasher to make a u64 which is easier to clone/copy and stuff and made a newtype around a PanelId. That way I could reuse the code from the previous paneling system to swap/manage open PanelIds, then just use find on the AppPanel vec to find the corresponding widget. Also, I never mentioned it but I’ve been trying RustRover for the past few devlogs. It’s a lot more stable than Zed, but I sorta miss its snappiness and minimalism. If I end up finding an annoying bug in RustRover, I’ll probably switch back. However, it’s been quite a few days and I don’t think I’ve found a single one. JetBrains is truly awesome at tooling. See ya next time. Probably will do my next ship soon :)
Had to remove the tracker.gg widget :( unfortunately they don’t allow scraping and what I had wasn’t allowed anyways. Things that used to open the player info widget now just open tracker in a browser.
Time formatting doesn’t suck anymore! I made the implementation way better so I could make it support stuff even past weeks, like months and years. The new implementation added around 60 lines, but it’s for a good cause. The previous implementation used a match statement with duplicated logic that only supported seconds, minutes, and hours, using a pluralize function and inline if-statements everywhere. If it were sent to any rational code-reviewer, they would likely have a heart attack. Now it’s a bit more complex but less complicated. You have TimeUnit s which format the unit themselves and TimePart s which format a value + a unit. Then, just use some math to build a Vec and format it into a string. Boom! Beautiful, well-formed time formatting. To soak in the new beauty, I added days n weeks. I just had to add 14 lines! The previous one would have probably needed a bunch of stupid magic numbers and weird chains everywhere to accomplish the same thing. Okay, I’m done using your time on reading a way-too-long write-up about fixing a crappy implementation that shouldn’t have been there in the first place. I’m gonna ship right after posting this. If you get it, make sure to give it 10 stars in everything 😉. Haha, jk. Well, not really. Cya later.
I’ve mostly been working on smaller features, improvements, and bugfixes. A cool one I did is fixing weird glitchy movement with the gamepad overlay window! Basically, the window needs to persist its position across disabling it/enabling it. And egui is immediate mode. So, every time it rendered the viewport, it would both set and get its position. So, moving it would update the current position, but because of the weird interop between setting the position in a ViewportBuilder and getting it from the viewport info, it would glitch back and forth, especially when my laptop is lagging. I fixed it by only calling the ViewportBuilder ‘s “ with_position ” method when the overlay had just been enabled ( !was_enabled && currently_enabled ). That way, it only sets it once externally, and lets it move freely while it’s open. Then, while it’s moving, it continues to update the saved position. And upon closing and then reopening it, it sets it again (but only once!) Relatively simple fix but it’s kind of funny. I mainly noticed it when I added a feature that would auto-unmaximize the overlay when the user maximizes it, and it would go crazy and fly around the screen. Very fun! I also fixed a pretty old bug where it would incorrectly detect the local player. Basically, the stats api doesn’t actually tell you who the local player is. So, we have to guess based on who the camera is targeting right now. Usually, it’s on the local player, but during goal replays or spectating a bot when joining a casual game, it would be improperly set at the start and then stay like that. So, I made sure that it always updated as long as the match is 1. in progress 2. not in a replay. It also fixes a thing with inconsistent match toasts, since the toasts only fire if the scoring player is the local player (and if it thinks the wrong player is the local player, everything’s messed up). I’m planning on revamping hotkeys next, to allow you to bind to any controller button and even do custom quickchats in-game. Later!
More gamepad overlay buttons!!! Yay!!! In not-so-awesomely-exciting news, I also added a table of contents to the readme (as well as a generator for it). The script’s in Python (I’m sorry for not using Rust) meaning the repository is now 0.5% Python. Takeover incoming? I overcomplicated the script a lot, but it’s really easy to read. There are three whole comments, aka 6% of lines in the whole file! It’s not technically interesting in the slightest. I was thinking of building a tree-like structure before outputting the string, but realized I can just use the header level (h1 vs h2 vs h3 etc) as the amount of spaces / 2. I also refactored it not to use magic PanelId s for the default panel list. I kinda rushed that part when I made it, I’ll be honest. But now each Panel can implement fn open_by_default(&self) -> bool (returns false by default) and if it does it keeps it open when savedata doesn’t exist yet. I don’t get how an entire hour n 27 minutes was spent on like 7 not-even-that-big commits. At least it was a good use of my math class. We reviewed limits for some reason (which everyone should already know for one because they took the prereqs?), and the teacher managed to extend her lecture for the ENTIRE class time. rlbuddy development saving the day, I guess (just like it will in your rocket league games btw, so you should download it)
I’ve been debugging high memory for the past month or so. It was egui_system_fonts. Why would fonts take 170mb of ram????????????????????????????????? help!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! I hate external dependencies!!!!!!!!!!!!!!!!!!!!11 I owe my life to DHAT now. go star https://github.com/nnethercote/dhat-rs
I did lots of performance improvements and also IT WORKS ON LINUX NOW!!! Let’s talk about performance first. Anyways, it has been using around 4% and 220MB of ram, which isn’t really what you’d expect from an overlay! So, I used things like DHAT to profile the memory, and noticed that fonts were doing some shenanigans. So I removed egui_system_fonts and loaded directly from C:\Windows\Fonts\segoeui.ttf and RAM usage dropped to 30mb. That was what the previous devlog was about. I then changed the default logic update rate from 10ms (100/s) to 65ms (15/s approx). This reduced idle cpu usage to under 0.2%. But, doing anything with the UI would spike it to 4%, which is still pretty bad. So, I used a method I found in an egui issue thread where I’d count the frametime since the last render, then sleep until the target frametime was finished. For example, if the frame rendered in 2ms but I want 16ms frametime, it would sleep for 14ms. That way, I could limit how often the UI rendered! This made it stay under around 1% CPU forever! Okay, Linux time! Rust is mostly cross-platform, so support was mostly dealing with dumbass design decisions. Since Music relies on GSMTC, I had to feature-gate the entire thing (as well as the windows/windows-future crates) behind cfg(windows) :( I also had to fix Linux font loading (since like I said earlier, it’s hardcoded to segoe ui). So I install fontdb on Linux and search for some popular fonts from there to load instead. Finally, I fixed up the storage paths since it could only load from AppData. I also made it use AppData\Local vs. AppData\Roaming instead of putting everything in Roaming. I first got it working through WSL! ! It took some messing with my GPU drivers but I got it to render. Now was the real challenge: getting it to build on an ACTUAL Linux machine. I had an old, basically broken Surface Pro 3 that I had put Debian+GNOME on for shits and giggles a few months ago. The perfect target! The main problem was getting Rocket League to even launch. I set up Legendary (goated launcher btw, check it out ) and tried to run it. I installed Wine, got Wine Mono, and it failed with a weird cryptic error about the CreateWindow function. The obvious solution to a graphical error: disable EAC. Yay, it launched! Then to get it to build. First I had to fix some filepaths since Linux is case-sensitive (nerds) while I don’t care about case on Windows. Then, rust-lld gave some weird error about simd or something which I didn’t care about. I just ran cargo clean and rebuilt it and it worked fine. Then to run, and it actually worked first try! It’s cool because I went into it expecting to debug a million socket errors, but it actuallly connected and ran. Awesome sauce! Now Linux users can use rlbuddy. More minions to contr- I mean cool users! Cya in the next one! ;) If you check out the rl perf overlay in the screenshot, you’ll be able to guess why this is the laptop I decided to put Linux on.
I’ve been putting this off for a while, but I’ve finally decided I’m going to add asset swapping. There’re a few existing implementations, so I’m reading those as well as some pdfs about the upk format. So basically credit to this document and this implementation . All it can do for now is parse the file summary. The implementation wasn’t too bad since I had Google as well as the C# implementation I just talked about. I basically have trait Deserializable : Sized { fn deserialize ( reader : & mut impl Read ) -> AnyResult < Self > ; } Then implement that for the various parts (eg. FGuid , a generic TArray ). Then, I use byteorder to read out numbers into each struct. For example, #[derive(Debug)] struct FCompressedChunkInfo { uncompressed_offset : i64 , uncompressed_size : i32 , compressed_offset : i64 , compressed_size : i32 , } impl Deserializable for FCompressedChunkInfo { fn deserialize ( reader : & mut impl Read ) -> AnyResult < Self > { Ok ( Self { uncompressed_offset : reader .read_i64 :: < LittleEndian > () ? , uncompressed_size : reader .read_i32 :: < LittleEndian > () ? , compressed_offset : reader .read_i64 :: < LittleEndian > () ? , compressed_size : reader .read_i32 :: < LittleEndian > () ? , }) } } I originally prototyped it in Python and then moved to Rust once I got tired of python’s byte shenanigans.
I JUST DESERIALIZED AND RESERIALIZED A UPK. OMAIGOD IM SO HAPPY. my brain is so tired. Alright so the payload still doesn’t get transferred but that’s pretty simple, it’s just Vec::extend. What a challenge! Deserialization actually ended up being the easy part, which is opposite of what I expected from starting the project. It went (relatively) fast since there are a few existing implementations which I could kinda just “borrow” structure parsing from. But, mine is unique in that it almost completely reserializes the package itself. I might’ve been able to “borrow” reserialization, but the impl I’ve been looking at is STRAIGHT ai slop and it’s more annoying to read that than to just do it myself. It was useful though, so I have to respect it. The first problem: basically, the first byte of an FString determines how many parts it has, NOT how many bytes it has. Which wasn’t a problem for parsing, since I had some word-based helpers which worked so I didn’t think much about it. But, when reserializing, I made two mistakes: 1. forgot to add the null terminator (which shouldn’t even be necessary) and 2. I accidentally put the wrong offset. I have no clue why there’s a null terminator for FStrings. The format is [n chars][the char bytes], meaning it should already be known how long the string is. Pretty stupid, but whatever. Anyways, when reserializing you’re supposed to include the null terminator in the string length marker. So, my logic was that I should offset it by two since unicode characters are two bytes. Bleh! I should’ve only incremented by one, since it’s in terms of utf16 chars rather than bytes. Yay, that worked! I don’t know how to summarize the next problem, so I’ll explain the reasoning. The package header is file summary + tables + something else (we’ll get to that later). Anyways, there’s some mysterious padding in between the summary and tables. Parsing, it’s easy to skip over because the summary includes the tables start offset ( name_offset , the name table is the first one). But when reserializing, you gotta keep those offsets intact. Well, I’m not totally sure if you actually have to keep them, but just for the sake of simplicity I tried to (which ended up adding more complexity, which I’ll see if I can remove now). So, I serialize/push the summary to the big Vec. Then, I make a new Vec and serialize the name table at (name_offset - summary_size), since the new Vec has to be encrypted and then appended to the big Vec. I do the same thing for imports/exports (placing the table at import/export _table_offset - summary_size ). Oh no! Since name_table_offset isn’t equal to summary_size, there’s some padding in between, which is expected. HOWEVER! The previous logic would skip over it in the table Vec creation, meaning the table Vec would have some 0-padding at the start. This padding needs to be in the SUMMARY part instead, and the name_offset should basically be the ZERO of the tables Vec! I doubt I would’ve been able to solve this part without the beautiful tool HxD . It’s really easy to use and is always helpful when I’m figgling around with binary. So, it does some more math, calculates the spacing, adds it to the big vec, and removes it from the tables Vec. Then, it re-encrypts the tables Vec and pushes it onto the big Vec! Okay, now that’s done. NOPE! There’s a mysterious structure for some reason at the end of the real table part? Yep, it’s that something else I said we’ll get to. I actually still have no idea what it is. From some research, I gathered it must be the dependencies table, but it’s really poorly documented. So, I ended up just cutting the bytes out from after the export table to the end of the tables part, and “pasting” it back before re-encryption. I do hope this devlog was readable. Now I’ll get to including the actual package payload and hopefully simplifying reserialization offsets. See you soon!
I figured out what comes after export tables! And it took way too long, since I DISCOVERED IT MYSELF! At least, as far as I know. I couldn’t find any information online about this portion of the header, so it was basically luck that I noticed a pattern. Before, I was just copying everything after the export table and pasting them back in afterwards, but as you can guess, that stopped working if I adjusted anything within the encrypted region. Compressed chunk info is a known part, and I figured out the AES padding pretty easily, so most of the time was spent trying to figure out what in the world those 24 bytes were. The first four bytes in the compressed chunk info are how many chunks they are. Here, in the Bubbles boost package, there are two (little endian), with 24 bytes of ???. And in Flamethrower, there are three, with 36 bytes of ???. I noticed that the ??? was always n chunks * 12 bytes (actually, it’s lucky that I didn’t test any which ended up having 0, otherwise I probably wouldn’t have figured it out). Since this probably had something to do with chunks, I made a guess that each chunk had some extra data stored in this array, 12 bytes of info per chunk. Using HxD’s highlight-bytes-and-get-the-decimal-value feature, I noticed a pattern where the first 8 bytes looked like an i64 offset and the last 4 looked like an i32 size, which the compressed chunk infos were also like for their matching counterparts. So, I deserialized it as [i64; i32] * n chunks and was able to read it better. These additional chunk infos seem to kinda sorta match up to uncompressed header offsets, but I don’t really understand what they are (and they’re marked as such in the code, lol). So, with that working (and some fiddling with broken serialization of TArrays which I’m surprised I didn’t catch earlier), I now understood the ENTIRE package header! Hooray! Let’s look at another example. body_grain_SF, the Fennec, has 4 chunks. Guess how many bytes of ??? it has. That’s right, zero! For some reason, a few packages just don’t have those things. After going through a bunch of them, I found that if licensee version was 32, there was no ???. But if it was 33, there WAS ???. I basically just added an if statement and it was fixed. I’m going to try to work on figuring out the padding in between the package summary and the start of the name table, but if I can’t, I suppose I’ll finally get to actually doing package swapping. Later!
I got a name swap! It doesn’t work properly, but I’m glad the game didn’t crash. These hours have been by far the most frustrating because the only way to test is against the real game since it’s really picky, but the game takes like 30 seconds to start, and if it crashes, it gives a cryptic error log, and if it freezes, it doesn’t give any log at all. I spent most of the time messing with moving different offset values forward and backwards, and ended up reverting to a simpler version with no overwriting when the header size changes just to test things. So far, I’ve mostly gleaned that name renaming is possible, but only up to a point. If the name grows too much, the game crashes, and if the name shrinks too much, the game freezes. Additionally, for some reason, total_header_size changes by the padded header size instead of the encrypted header size, which I still don’t understand. It’s seriously weird. I serialize each name properly, and yet the game reads it wrong. This game really does need a new codebase. When UE6 comes out, either the game’s gonna be so good it doesn’t need mods, or it’ll be relatively easily moddable. Or neither, which is the most probable based on Epic Games’ track record. Anyways, enjoy the beautiful screenshot.
IT SWAPPED IT SWAPPED IT SWAPPED AHAHAHHAHAHAHHAHAHAHAHHA FINALLY Okay now I gotta make it work for other pairs of boosts. If WHEN that works, I’ll actually put it in rlbuddy. I removed all the code relating to changing offsets to try to give myself a minimal workspace to tinker in, with as little side effects as possible. So, all the current code does is 1. open them 2. replace the name table entries and package guid. Surprisingly simple. Once I make it a bit more generic, I can extend it to support more swaps. If I’m really unable to get it to work for more combinations, I’ll probably end up just publishing the poc repo and not adding swapping to rlbuddy at all. This is a great motivation boost, though. I was honestly dreading coming home to work on it today because I was done with failing over and over, and considering giving up.
IT WORKS WHEN THE HEADER EXPANDS NOW YAYAYYAYAYAYYAYAYYAYAYAY I’m gonna be honest, the problem might just have been that my testing package was corrupted. I was debugging my deserialization logic, which I thought might have some parsing issue near the header extraction, and noticed that once the encrypted region deserializer’s Cursor reached the end of the region, it was actually FARTHER than the package summary’s total_header_size - by 6 bytes! That’s pretty bad, and I don’t know how something like that even happened. Anyways, I repaired the game and brought over the shiny new upk and… it worked. I’m pretty sure that I only accidentally did this while tidying up my workspace a few hours ago, but if this has genuinely been the thing holding me back for the past few days - I also finally made the poc code public, at https://github.com/kalilamodow/rl-upk-testing/
Apparently there are a bunch of different AES keys rocket league uses, not just one. You can get a list of them from the psynet config url, and then do some weird crc stuff to change them from “content keys” to aes keys. I discovered the rocket league mapmaking discord today and I’m blown away by the amount of information on there, and also apparently my whole thing about the unknown bytes when licensee version > 32 was straight up wrong, which is funny. Anyways it works for all items now, which is great. I’m gonna work on actually putting it in rlbuddy now, hopefully that goes well
rlbuddy now has item swapping! Basically, making one item appear like another to use items you don’t actually own. These hours were spent on a few different things. Firstly, I optimized the thing a bunch! There were a few places that were pretty slow since the original repo was mainly for testing. For example instead of decrypting the entire header and rechecking it to find the right key, it only loads the first 4 blocks and attempts to decrypt the first FString. If it fails, that means the AES key isn’t valid. Also, I improved the error handling with anyhow. It actually makes it so much easier to deal with things and was pretty useful when debugging the move over to the actual app. Now I’m just going to work on polishing the panel UI a bit more, since it’s still kind of ugly.
I made a ton of improvements to the asset swapper! And fixed a bug with the music player which is cool technically, so I’ll start with that. The player uses the GSMTC api, which is async, but egui doesn’t really like async. So, I made it so that every time you wanted to run some command on the current session, it would start up a new thread. This was less than ideal for a few reasons: terrible performance race conditions could happen just… in Windows itself The main problem was 2: for example, if you skipped a replay too fast, the Play command from the end of the replay might actually reach Windows before the Pause command from the replay start, leaving your music paused. So instead, it starts one dedicated worker thread which has a channel which receives a Box<dyn FnOnce(Session)> , and executes at most one command every 50ms (only one command per loop, fixed loop time of 50ms). It’s really interesting because you actually end up sending a closure through a channel, which feels funny but when you really think about it it’s just ownership semantics. But, that’s working great now! On to swapper improvements! First, I moved most of the swapper module to anyhow handling (it’s heaven!), and I also modernized the upk api a bit since it was basically dragged directly from the proof-of-concept repo, meaning it was not really application-ready. Also, it previously looked at the game’s webcache to decode the AES keys for header decryption, but now it uses the wonderful Toga-Files repository along with a really nice caching layer for it to load items and their associated API keys. Having access to the actual item data is also way better since you don’t have to manually type in a package name which is kinda weird for any normal person. Now, you can select it from a dropdown instead! I love UX.
rlbuddy now loads additional playlists straight from the game’s cached webconfig, instead of hardcoding them all! This means that the list of playlists will basically never go out of date. There are actually quite a few services which rely on game files (eg. auto stats api setup, custom maps, asset swapper). So, the first thing I did was to refactor it so make all of these load from the same global value. Then, for playlists, I made it read from a specific file in the game’s http cache directory which contains a bunch of configuration. Then, it parses it into a JSON object, grabs all the values with Class PlaylistSettings_TA, and then extracts the title and playlist id! There are still a bunch of built-in Playlists, since those have internal things like going from a casual playlist type to its respective competitive one or having the hard-coded list of singleplayer playlists. But, all the other ones, like ltms are dynamically loaded. Yay!
I got licensee version 34 (encrypted payloads too) to swap finally! Okay, so this was awesome. Rocket League’s been having a bunch of updates lately, which breaks the packages of people trying to get free stuff. So, in a Reddit thread, I ended up dishing out a bunch of swapped asset files for people in this situation. There’s a new boost which everybody wanted really bad since it was really rare (but they decided to make it unlockable at rocket pass lv99 so it doesn’t even matter anymore), and it was UNSWAPPABLE for three reasons: it was encrypted specially i hadnt encountered specially encrypted packages meaning their handling was wrong aes keys werent out for it yet (or so I thought) I’ll start with 3. I had written a tool which looks in the games cache for AES keys, and dumped them, and it worked great. What I didn’t expect is the filename to change between versions. So, I was super confused since yesterday since the AES key for the boost was just… missing. Thankfully, the geniuses at the RLMM discord got it and helped me figure out that the file I was reading from was just outdated. Great! Anyways, not too big of a deal, just a big delay. Now it’s time for the real challenge: figuring out how the hell to handle this type of package. First, I did some research. The twelve bytes of padding in files with licensee version >=33 before the summary are actually a nonce/aes iv, not just padding. Then, the header is encrypted with AES-CTR with the twelve first bytes of the iv just being the nonce. And the compressed chunks are encrypted too here: each one is also AES-CTRed with their own nonces in FCompressedChunkInfo. Thing is, I don’t have an implementation to really go off anymore, meaning there’s a lot more trial and error. For the original implementation, there were one or two existing asset swappers (though they were vibecoded meaning more of a puzzle to figure out what they’re even doing), but this time I was mostly relying on emii’s fork of UEViewer. Anyways, after spending a while figuring out what exactly I had to masquerade (payload, key, swap nonces) I finally got it to work!
Not much in terms of super-cool stuff this time. Highlights are: make sure dynamic playlist loading uses the latest cache file (and also fix its loading on normal people’s rocket league installs) loading the cache file on another thread so it doesnt freeze the whole app for a second on the first lazy lock access searching for items in the item swapper moved the error handling of the map loader to anyhow So no ultra-exciting things to talk about. anyhow is really cool though, it really simplifies error handling everywhere in the app, and I’m a big fan of its bail! macro. I’m also using Cow a lot more. Moo!
I made a standalone web-based swapper for the people who don’t want to download an entire app. A lot of people had broken packages after the last update but didn’t want to install a dedicated program just for a few swaps. So, I used the asset swapper backend of rlbuddy to make this thingamabob which runs entirely in the browser! First, I had to find out how to actually share rlbuddy’s asset swapper with other projects. Turns out Cargo has a cool thing called workspaces where you can have multiple crates in the same repo! So I moved the dedicated UPK/AES etc. stuff into a new crate and added it as a dependency in regular rlbuddy. Then, I whipped up a small wasm-compatible eframe app with the template, and included the new crate as a dependency. I made a big state machine of the steps meaning it’s impossible to have an invalid state, which is really nice. The only problem was that Rust’s ownership system isn’t smart enough to deal with it, so the code is a bit repetitive in places but it’s definitely worth it. The WASM restrictions were super annoying, mainly do to with filesystem access and requiring async. I ended up using the egui-async crate and spilling Binds all over the place within the state machine. I couldn’t even figure out how to get the actual swap procedure to run in a different thread. I also spent like 30 minutes debugging why in the world asset swapping was just broken. Even when I verified the same binary data were going in to the Upk object in the webapp and rlbuddy, for some reason the output upk was just different. Turns out I used chosen_replacement.name instead of chosen_replacement.package.name() . Isn’t that great? Anyways, it works fine now and I’m planning on posting it on r/RocketLeagueMods and possible r/RocketLeague.
A few fixes here and there. Some refactors, caching externally loaded playlists in savedata, and fixing a deadlock when clearing the items cache. And for some reason the decal option was missing from the items list picker.
more small improvements. i gave the session stats widget an actual service as well which speeds things up a lot since its not recalculating statistics every single frame. the swapper also uses the backup file if one is available and it no longer accepts duplicate custom maps
Ships:
rlbuddy is a feature-filled companion for Rocket League! It shows the skill of everyone in your lobby and has tons of toggleable widgets and features to make your life a lot easier, from Discord integration to music control. This is my first non-tiny Rust project, and I think it was really great! I’ve learned a lot such as proper state management and the ins and outs of Rust’s syntax. You can give it a try by heading to the Github releases section. If you play Rocket League, I hope you use it!
rlbutddy is a feature-rich companion for Rocket League, and v1.2 is out. There’s been some big architectural changes meaning it’s faster and bug-free-er, as well as big new features! New!!! Avatars! Now you can see people’s profile pictures even when you’re not on their platform. Custom map loader! You can import maps from a ZIP or download them straight from Bakkesplugins! Gamepad overlay! Show your controls while playing, to show off your bindings or get some help with your speedflips. I think I’ve definitely improved at Rust since the last release. Just in terms of the amount of mistakes I make, I feel like it’s way more smooth sailing than before. For example, I was able to (mostly) write not-so-buggy code and “run the borrow checker in my head” without needing to cargo check a million times, and clippy tends to have a lot less suggestions for me. Pretty great!
I made rlbuddy, a rocket league companion app! This is the third ship so far. The big features this time are the asset swapper and Linux support, but I think that the feature I’m most proud of this time is the asset swapper! It inserts the appearance of one item into the file of another, meaning by swapping out assets for others you can make your car look however you like! (Client-side only, of course). I learned a lot about byte-level manipulation and working with raw data streams and encryption/decryption in Rust, and I can read hex (relatively) well now! It’s also great because I think this combined with the doorstop experiment from a while ago gives me a nice base to try some reverse-engineering challenges in the future. I also worked a lot on performance this time, such as tuning loop times/ui fps caps and minimizing ram usage (I achieved around an 80% reduction here!) There are a crap ton more features added than that (totaling almost 100 commits), but I’m pretty sure you’re not supposed to have massive feature-dumps on the ship text. So the full list is on the latest github release! In the image, you can see I have a white fennec. In reality, I don’t actually have a tw trim, and the equipped decal isn’t compatible with the fennec either. But, using the power of asset swapping, I can get a beautiful all-white car. Neat!...
|
1793253004.0502186
|
1793339404.0502186
|
|
65608
|
rnbill by @rnabilaltamis14 | Stardance
|
rnabilaltamis14
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: rnbill by @rnabilaltamis14 | Stardance
|
1793090858.1510558
|
1793695658.1510558
|
|
52500
|
roShaN OS by @projectroshan917 | Stardance
|
projectroshan917
|
2
|
5.0
|
2
|
[{"ship_num": 1, "ts": "2026-0 [{"ship_num": 1, "ts": "2026-08-24T12:37:13Z", "hours": 1.0, "multiplier": 2.06, "payout": 2, "blessing": "neutral", "text": "My website be more reliable to use because i add a working webpage with multiple draggable windows and with multiple games and apps to use them at once as well.", "repo": "https://github.com/Roshan-coder123/roShaN-OS", "demo": "https://roshan-coder123.github.io/roShaN-OS/"}, {"ship_num": 2, "ts": "2026-08-22T09:03:10Z", "hours": 4.0, "multiplier": null, "payout": 0, "blessing": "neutral", "text": "This is roShaN OS which is based on my name roShaN OS where you can use multiple apps and games as well websites. Features: Websites: You can access my My portfolio website My Boba website NASA gallery Google Apps: You can access app like: Calculator Also can play games like tick tac toe and snake game You can get some info about me in about Roshan section. You can save your sticky notes here. And You can listen 11 different musics here without internet. You can Customise roShaN OS according to to your need and choice by selecting colour theamnad changing background here.", "repo": "https://github.com/Roshan-coder123/roShaN-OS", "demo": "https://roshan-coder123.github.io/roShaN-OS/"}]...
|
[{"post_id": 53744, "ts": "202 [{"post_id": 53744, "ts": "2026-08-24T12:34:36Z", "hours": 0.3325, "text": "I have a working webpage with multiple draggable windows. Also i change my css code to make my OS more smoother and reliable to use."}, {"post_id": 52404, "ts": "2026-08-22T15:05:44Z", "hours": 0.4636, "text": "Made better to my OS by adding dragging options for apps and make better in mobile device."}, {"post_id": 52167, "ts": "2026-08-22T08:06:59Z", "hours": 1.315, "text": "Changing background pic options and added 11 music on music app which can run without internet as well. Background options on roShaN OS are: 1 defult, 2 grid, 3 stars, 4 circuit."}, {"post_id": 52042, "ts": "2026-08-22T02:45:05Z", "hours": 3.0617, "text": "My first own OS “roShaN OS”"}]...
|
Title: roShaN OS by @projectroshan917 | Stardance
Title: roShaN OS by @projectroshan917 | Stardance
Devlogs:
My first own OS “roShaN OS”
Changing background pic options and added 11 music on music app which can run without internet as well. Background options on roShaN OS are: 1 defult, 2 grid, 3 stars, 4 circuit.
Made better to my OS by adding dragging options for apps and make better in mobile device.
I have a working webpage with multiple draggable windows. Also i change my css code to make my OS more smoother and reliable to use.
Ships:
This is roShaN OS which is based on my name roShaN OS where you can use multiple apps and games as well websites. Features: Websites: You can access my My portfolio website My Boba website NASA gallery Google Apps: You can access app like: Calculator Also can play games like tick tac toe and snake game You can get some info about me in about Roshan section. You can save your sticky notes here. And You can listen 11 different musics here without internet. You can Customise roShaN OS according to to your need and choice by selecting colour theamnad changing background here.
My website be more reliable to use because i add a working webpage with multiple draggable windows and with multiple games and apps to use them at once as well....
|
1792875275.2741797
|
1793739275.2741797
|
|
55448
|
roShaN OS by @projectroshan917 | Stardance
|
projectroshan917
|
1
|
11.0
|
0
|
[{"ship_num": 1, "ts": "2026-0 [{"ship_num": 1, "ts": "2026-08-28T13:34:04Z", "hours": 11.0, "multiplier": null, "payout": 0, "blessing": "neutral", "text": "This OS is based on my name roShaN OS where you can use multiple apps and games as well websites. Features: Websites: You can access my My portfolio website My Boba website NASA gallery Google Apps: You can access app like: Calculator Also can play games like tick tac toe and snake game You can get some info about me in about Roshan section. You can save your sticky notes here. And You can listen 11 different musics here without internet. You can Customise roShaN OS according to to your need and choice by selecting colour theamnad changing background here. To make this OS AI is also used.", "repo": "https://github.com/Roshan-coder123/roShaN-OS", "demo": "https://roshan-coder123.github.io/roShaN-OS/"}]...
|
[{"post_id": 56618, "ts": "202 [{"post_id": 56618, "ts": "2026-08-28T13:31:56Z", "hours": 11.1761, "text": "I made my own named customized web OS."}]...
|
Title: roShaN OS by @projectroshan917 | Stardance
Title: roShaN OS by @projectroshan917 | Stardance
Devlogs:
I made my own named customized web OS.
Ships:
This OS is based on my name roShaN OS where you can use multiple apps and games as well websites. Features: Websites: You can access my My portfolio website My Boba website NASA gallery Google Apps: You can access app like: Calculator Also can play games like tick tac toe and snake game You can get some info about me in about Roshan section. You can save your sticky notes here. And You can listen 11 different musics here without internet. You can Customise roShaN OS according to to your need and choice by selecting colour theamnad changing background here. To make this OS AI is also used....
|
1792878557.343827
|
1793742557.343827
|
|
6730
|
roadtoicpc by @climiealex | Stardance
|
climiealex
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: roadtoicpc by @climiealex | Stardance
|
1792716603.814732
|
1793321403.814732
|
|
59596
|
roberts game development by @RobertSchmutsch | Sta roberts game development by @RobertSchmutsch | Stardance...
|
RobertSchmutsch
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: roberts game development by @RobertSchmutsc Title: roberts game development by @RobertSchmutsch | Stardance...
|
1792806550.1383445
|
1793411350.1383445
|
|
53798
|
robit by @arcadewise | Stardance
|
arcadewise
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: robit by @arcadewise | Stardance
|
1792759941.6025987
|
1793364741.6025987
|
|
1171
|
roblox game ad by @az01pak | Stardance
|
az01pak
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: roblox game ad by @az01pak | Stardance
|
1793227973.700507
|
1793832773.700507
|
|
67776
|
roblox game by @bullseye | Stardance
|
bullseye
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: roblox game by @bullseye | Stardance
|
1793112937.7986722
|
1793717737.7986722
|
|
43669
|
roblox game! by @estella | Stardance
|
estella
|
0
|
22.76
|
0
|
[]
|
[{"post_id": 76817, "ts": "202 [{"post_id": 76817, "ts": "2026-10-04T18:13:46Z", "hours": 2.2119, "text": "computa…. how to auto resize gui? So I’ve spent the last 2 hours fixing the gui so it would display correctly on different screen dimensions. There was probably an easier way to do this, but I went through and converted all the dimensions by dividing the larger frame, in which the small frame is located in, by the smaller frame"}, {"post_id": 74698, "ts": "2026-09-28T01:02:20Z", "hours": 4.4922, "text": "More work on the gui, I have been working on writing the actual story and man this is harder than I thought… Anyway, I lost my github account to 2fa rip… In addition to working on the story, I have been building the next room which will have an abandoned science lab feel. I am having some trouble figuring how to make the unlocked entries stay unlocked even after the player leaves the server. So far the points the player wins from sodoku saves, so thats a win I guess! I think I’m going to have to ship the game in a super unfinished state because I’m running out of time):"}, {"post_id": 50315, "ts": "2026-08-19T14:05:33Z", "hours": 4.9433, "text": "I see that I have been spelling sudoku wrong this entire time oops, anyhow, I have started work on the gui and point system. The plan is to have datapoints received from different difficulties of sudoku completed, then use those points to unlock entries from an unknown database. So far I’m not sure if the gui aesthetic really matches grrrr but hopefully as the game comes together it blends better. I ended up abandoning the idea of using runic characters in place of numbers on the sudoku board, as I couldn’t figure out how to get the images to display correctly sigh I did however, replace the zeros with blank tiles so its easier to see and instead of cycling through to zero, it will go back to a blank tile."}, {"post_id": 47009, "ts": "2026-08-14T16:15:40Z", "hours": 5.0586, "text": "So far I have finished all the sodoku logic, which includes a solver, remove cell function, shuffle positions, and generate numbers. Now I am working on the map design. The current task at hand is figuring out how I can import custom images for numbers while keeping all the logic the same"}, {"post_id": 46023, "ts": "2026-08-12T22:31:14Z", "hours": 6.0519, "text": "Dev log 1 for my Roblox game! I am planning on making multiple games, each focused on a different core mechanic of the main game I want to make. I have finished the first mini logic game I plan on implementing so far, which is Sudoku. I created an empty board with gui to display text and labeled each with a 0. I then created an algorithm for checking if a row/column/box is valid and a win check. I then pasted a solved puzzle into the table and removed a few numbers to ensure my checking functions were correct. After, I made a solver, then backtracked to make a puzzle generator. I implemented easy, medium, hard, and extreme modes, which control how many cells will be removed from the generated puzzle."}]...
|
Title: roblox game! by @estella | Stardance
Devlo Title: roblox game! by @estella | Stardance
Devlogs:
Dev log 1 for my Roblox game! I am planning on making multiple games, each focused on a different core mechanic of the main game I want to make. I have finished the first mini logic game I plan on implementing so far, which is Sudoku. I created an empty board with gui to display text and labeled each with a 0. I then created an algorithm for checking if a row/column/box is valid and a win check. I then pasted a solved puzzle into the table and removed a few numbers to ensure my checking functions were correct. After, I made a solver, then backtracked to make a puzzle generator. I implemented easy, medium, hard, and extreme modes, which control how many cells will be removed from the generated puzzle.
So far I have finished all the sodoku logic, which includes a solver, remove cell function, shuffle positions, and generate numbers. Now I am working on the map design. The current task at hand is figuring out how I can import custom images for numbers while keeping all the logic the same
I see that I have been spelling sudoku wrong this entire time oops, anyhow, I have started work on the gui and point system. The plan is to have datapoints received from different difficulties of sudoku completed, then use those points to unlock entries from an unknown database. So far I’m not sure if the gui aesthetic really matches grrrr but hopefully as the game comes together it blends better. I ended up abandoning the idea of using runic characters in place of numbers on the sudoku board, as I couldn’t figure out how to get the images to display correctly sigh I did however, replace the zeros with blank tiles so its easier to see and instead of cycling through to zero, it will go back to a blank tile.
More work on the gui, I have been working on writing the actual story and man this is harder than I thought… Anyway, I lost my github account to 2fa rip… In addition to working on the story, I have been building the next room which will have an abandoned science lab feel. I am having some trouble figuring how to make the unlocked entries stay unlocked even after the player leaves the server. So far the points the player wins from sodoku saves, so thats a win I guess! I think I’m going to have to ship the game in a super unfinished state because I’m running out of time):
computa…. how to auto resize gui? So I’ve spent the last 2 hours fixing the gui so it would display correctly on different screen dimensions. There was probably an easier way to do this, but I went through and converted all the dimensions by dividing the larger frame, in which the small frame is located in, by the smaller frame...
|
1793231836.287682
|
1793836636.287682
|
|
42572
|
roblox plane animation by @Jonnomam | Stardance
|
Jonnomam
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: roblox plane animation by @Jonnomam | Stard Title: roblox plane animation by @Jonnomam | Stardance...
|
1793181223.0938656
|
1793786023.0938656
|
|
42573
|
roblox plane animation by @Jonnoman | Stardance
|
Jonnoman
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: roblox plane animation by @Jonnoman | Stard Title: roblox plane animation by @Jonnoman | Stardance...
|
1793181223.9839034
|
1793786023.9839034
|
|
44274
|
robo arm by @jeffreyli | Stardance
|
jeffreyli
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: robo arm by @jeffreyli | Stardance
|
1793264208.0015564
|
1793869008.0015564
|
|
33562
|
robo jump by @damoose1311 | Stardance
|
damoose1311
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: robo jump by @damoose1311 | Stardance
|
1792749003.0565474
|
1793353803.0565474
|
|
32129
|
roboPet by @aryavora21 | Stardance
|
aryavora21
|
0
|
23.63
|
0
|
[]
|
[{"post_id": 24529, "ts": "202 [{"post_id": 24529, "ts": "2026-07-08T04:30:26Z", "hours": 1.21, "text": "Working on cad design for the frame"}, {"post_id": 24156, "ts": "2026-07-07T15:19:03Z", "hours": 22.4206, "text": "Wireless Pico UART REPL via Pi Zero 2W What I worked on: Executed the dual-board bridge bring-up: connecting the Raspberry Pi Pico (real-time controller) to the Pi Zero 2W (high-level brain) over a physical UART link. Wired Zero GPIO 14/15 (mini UART TX/RX) to Pico GP1/GP0 (UART0 RX/TX) on a shared power rail, with SWD on GPIO 24/25 for flashing. After flashing fresh MicroPython v1.23.0 via OpenOCD over SWD, set up the MicroPython REPL to duplicate over UART so the Zero can wirelessly talk to the Pico.Hit two blockers: wiring polarity was wrong initially (TX-to-TX, RX-to-RX instead of a null-modem crossover), and os.dupterm(uart, 1) crashed boot.py since RP2040 MicroPython only supports dupterm slot 0. Both fixed, and added core_freq=250 to the Zero’s config.txt to stop the mini UART baud rate drifting with CPU frequency scaling.Followed up (this session): wrote tools/blink.py and uploaded it to the Pico via tools/upload_to_pico.py, confirming the full host -> SSH -> Zero -> UART -> Pico pipeline end-to-end. What I learned: Embedded Trap: RP2040 MicroPython’s os.dupterm() only supports slot index 0. Slot 1 is silently invalid, causing a boot-time crash invisible unless you’re watching USB REPL output. Use os.dupterm(uart) or os.dupterm(uart, 0). Hardware Lesson: Pi Zero 2W mini UART (ttyS0) derives its baud rate from the VPU core clock. Without core_freq=250, frequency scaling causes the baud rate to drift; even a 1% mismatch causes data loss. SSH Issue: After rebooting the Zero, the SSH host key changed, blocking password login until passing -o StrictHostKeyChecking=accept-new.What’s next: Now that the wireless UART REPL bridge and upload pipeline are verified, next is wiring an MPU6050 + two servos on the Pico for a simple tilt-correction gyroscope test, adding an OLED pitch/roll readout, and smoke-testing the Zero’s PiCam/mic/speaker independently."}]...
|
Title: roboPet by @aryavora21 | Stardance
Devlogs Title: roboPet by @aryavora21 | Stardance
Devlogs:
Wireless Pico UART REPL via Pi Zero 2W What I worked on: Executed the dual-board bridge bring-up: connecting the Raspberry Pi Pico (real-time controller) to the Pi Zero 2W (high-level brain) over a physical UART link. Wired Zero GPIO 14/15 (mini UART TX/RX) to Pico GP1/GP0 (UART0 RX/TX) on a shared power rail, with SWD on GPIO 24/25 for flashing. After flashing fresh MicroPython v1.23.0 via OpenOCD over SWD, set up the MicroPython REPL to duplicate over UART so the Zero can wirelessly talk to the Pico.Hit two blockers: wiring polarity was wrong initially (TX-to-TX, RX-to-RX instead of a null-modem crossover), and os.dupterm(uart, 1) crashed boot.py since RP2040 MicroPython only supports dupterm slot 0. Both fixed, and added core_freq=250 to the Zero’s config.txt to stop the mini UART baud rate drifting with CPU frequency scaling.Followed up (this session): wrote tools/blink.py and uploaded it to the Pico via tools/upload_to_pico.py, confirming the full host -> SSH -> Zero -> UART -> Pico pipeline end-to-end. What I learned: Embedded Trap: RP2040 MicroPython’s os.dupterm() only supports slot index 0. Slot 1 is silently invalid, causing a boot-time crash invisible unless you’re watching USB REPL output. Use os.dupterm(uart) or os.dupterm(uart, 0). Hardware Lesson: Pi Zero 2W mini UART (ttyS0) derives its baud rate from the VPU core clock. Without core_freq=250, frequency scaling causes the baud rate to drift; even a 1% mismatch causes data loss. SSH Issue: After rebooting the Zero, the SSH host key changed, blocking password login until passing -o StrictHostKeyChecking=accept-new.What’s next: Now that the wireless UART REPL bridge and upload pipeline are verified, next is wiring an MPU6050 + two servos on the Pico for a simple tilt-correction gyroscope test, adding an OLED pitch/roll readout, and smoke-testing the Zero’s PiCam/mic/speaker independently.
Working on cad design for the frame...
|
1793296500.6519868
|
1793901300.6519868
|
|
40940
|
robocar thingy idk by @neonxsl | Stardance
|
neonxsl
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: robocar thingy idk by @neonxsl | Stardance
|
1792781658.0007036
|
1793386458.0007036
|
|
31875
|
robochicken by @xChicken | Stardance
|
xChicken
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: robochicken by @xChicken | Stardance
|
1792727269.142238
|
1793332069.142238
|
|
33211
|
robot - intake and frame by @kaylawong | Stardance
|
kaylawong
|
1
|
65.0
|
0
|
[{"ship_num": 1, "ts": "2026-0 [{"ship_num": 1, "ts": "2026-08-18T06:59:07Z", "hours": 65.0, "multiplier": null, "payout": 0, "blessing": "neutral", "text": "Alongside a team of 2 other high schoolers, I helped to make a robot that can intake balls, shoot balls, and navigate across an obstacle course. It was originally intended to be autonomous, but we wanted to focus more on the mechanical systems that were on the robot. I was in charge of designing and assembling the intake subsystem. It was very challenging to fit within the design constraints that we were provided but it was very rewarding once I saw my CAD come to life! This was my first time having something I designed online turn into something physical, so I was very surprised that when all my parts got shipped that it all came together nicely. I did this under a summer program in collaboration with Stardance called Definity Education Summer Camp and it was fun going through trial and error to see what worked and didn’t work. The hardest part was definitely having to make everything from scratch and not buying pre-made FTC or VEX parts, like getting our sheet metal manufactured was a risk in itself! But it was totally worth it.", "repo": "https://github.com/kwong09/definity-robot-project", "demo": ""}]...
|
[{"post_id": 49407, "ts": "202 [{"post_id": 49407, "ts": "2026-08-18T06:55:42Z", "hours": 0.7392, "text": "i just updated our limit switch arm because it ended up snapping!! i thickened the arm and i also included a filleted part to prevent it from snapping so easily. that’s it for today!!"}, {"post_id": 45502, "ts": "2026-08-12T01:25:13Z", "hours": 1.4008, "text": "i finally have a good idea on how to slice for bambu printers!! i did a lot of 3d printing today, and i started by reprinting the side funnel walls . i also helped to make an attachment so that the limit switch would be pressed once the intake has pivoted past a certain point, to make sure it doesn’t touch the ground. it is definitely not the best solution but i was able to make it work with a bunch of tape and 3d print iterations !!"}, {"post_id": 45015, "ts": "2026-08-11T04:49:30Z", "hours": 1.3597, "text": "we finally got our intake and shooter to work! i mostly helped by tensioning the neoprene rubber strips and using rubber glue to secure it. earlier, i was trying to make a 3d print to make it easier to cut the rubber strip so that it can be cut at an angle, allowing me to overlap the rubber without making the conveyor too thick. however, that didn’t end up working because the rubber was difficult to cut, so we just decided to glue one on top of the other. one issue we ran into was that on the overlapped portion, it would jam into the pulley for the shooter. this was eventually fixed by cutting the part of the rubber conveyor that was too thick and would hit the pulley! with the extra scraps of rubber that i messed up on, i made a little dude with scissors"}, {"post_id": 42302, "ts": "2026-08-06T23:08:39Z", "hours": 2.22, "text": "i had to take apart and reassemble the intake because the pulleys were on the wrong side… since the pulley on the motor shaft doesn’t fit over the set screw on the shaft, the final belt had to be on the opposite side, so i had to take everything apart :’) my teammate attached the shooter system onto the intake after i was done, so our robot is finally coming together!!"}, {"post_id": 41685, "ts": "2026-08-06T01:53:41Z", "hours": 4.1467, "text": "i worked on assembling the intake, which was mostly the last part of the day today. we got our metal parts in the afternoon ish, so earlier in the day i was working on the guides to funnel the balls into the intake. i also continued refining the cad so now it looks like this!! the intake itself is working decently okay, i have to attach the motor tomorrow. one problem is that i have to wrap around the rubber onto the conveyor, otherwise there is no contact from the rollers on the conveyor to the tennis balls."}, {"post_id": 40444, "ts": "2026-08-03T23:22:04Z", "hours": 1.8822, "text": "on friday and today, i mounted the 3d prints to the shafts to start assembling the intake. i was struggling to attach a lot of these parts to the shafts because a layer of the prints kept on stopping the shaft from going all the way through. someone else had to hammer the parts onto the shafts in order for it to work :’) we’re still waiting on our metal part that will contain all of our intake parts, but we do have other components printed. our rollers were printed using TPU, and we also got our pulleys and shaft collars finished as well. we also have very very colorful filament now!!"}, {"post_id": 38275, "ts": "2026-07-31T16:14:36Z", "hours": 3.4442, "text": "yesterday i worked on making the shaft collars and pulleys to print out. our pulley prints were not very successful, but the shaft collars turned out great. i downloaded a free shaft collar file from this link and i changed the size of the screw hole, the width, and i adjusted it so there were two flat ends (so we could use a screw and nut). my team also cut the shafts so we can start making our intake"}, {"post_id": 37470, "ts": "2026-07-30T07:37:50Z", "hours": 4.8083, "text": "today i iterated more on the design of the intake. after printing the pulleys, we realized the belt was slipping and skipping teeth on the pulleys because we were using GT2 belts. we’re now pivoting to HTD-5M belts so we can hopefully not have any slipping happen on our intake. we also ordered more parts and added them to our purchase list!! when the sheet metal comes, we will be able to test the fit of bearings and other parts as well."}, {"post_id": 36860, "ts": "2026-07-29T05:58:16Z", "hours": 3.6517, "text": "here’s an update on what the robot has been doing! i haven’t been working much on the driving part, so my teammates did a majority of figuring out how to navigate the course and how to drive. our robot was able to make it over the hill (but it flipped over if you watch the video lol). i am still working on the intake, but i have gotten further by printing out some pulleys and some shaft to bearing attachments we might use on the robot. tomorrow we will hopefully order sheet metal to hold our intake system. also, one of our prints turned into a blob"}, {"post_id": 36248, "ts": "2026-07-28T06:55:01Z", "hours": 3.0003, "text": "some of our parts for the intake arrived, so i was able to design more parts of our intake around it. i adjusted the rollers and came up with two different iterations, depending on the width of the rubber we will use to wrap around the rollers. i also measured the diameter of the hex shafts that arrived, which let me test printing out GT2 gears. i edited the gear by finding a part on McMaster-Carr and cutting out the hex shape and chopping off the extruded portion."}, {"post_id": 34463, "ts": "2026-07-25T06:02:53Z", "hours": 2.2911, "text": "i worked on our shooter design, coming up with different ways to get the tennis balls into a bucket while fitting into our size constraints. especially with having a pivot on our intake, having the motors for the flywheels in the back would add too much weight, so i am still figuring out how to get that to work. i am testing out different orientations for the motors and flywheel, such as a dual horizontal flywheel shooter and a single flywheel shooter."}, {"post_id": 33908, "ts": "2026-07-24T06:27:49Z", "hours": 3.1067, "text": "today, i worked on editing our parts that will be machined for our intake. instead of making it three pieces, we realized we could just bend one large piece of metal. for the motor mount, i added a way to allow it to tension so that when we add belts, we can adjust accordingly. i also ordered all the parts we need for the intake system, including the shafts, motor, bearings, etc. lastly, we also started planning out how our shooter would work, which we chose to do two flywheels at the end of the conveyor."}, {"post_id": 33540, "ts": "2026-07-23T16:02:48Z", "hours": 2.7739, "text": "i mostly researched different parts to purchase and further refined how the intake would work. to run the conveyor, one motor would power the first roller, then the rest would be connected by pulleys and belts. i also added a motor mount to the side plate of the intake. for the chassis itself, i extended the base plate and L-brackets so that it would be 15x15 inches, which was one of the constraints."}, {"post_id": 33069, "ts": "2026-07-22T17:19:41Z", "hours": 4.6258, "text": "yesterday i worked on combining our intake system with a pivot. i realized that there was very little space between both motors, so i moved the pivot between where two motors are, where a shaft would be placed under the intake. however, in order to fit under the 8 inch limit, the pvc pipes would have to be smaller or the walls of the intake would have to be shorter. i also started putting other components into the CAD, including the bearings, pipes, and pulleys that might be on the robot. lastly, i tried to modify the bottom pan of the robot’s frame to allow for the intake to turn downwards at a more drastic angle."}, {"post_id": 32206, "ts": "2026-07-21T06:57:44Z", "hours": 3.6667, "text": "today, my teammates and i first worked on getting the 3d printed center parts of our wheels to fit. the pneumatic wheels were really difficult to work with, especially because the prints weren’t fitting on perfectly (it took us like 1-2 hours). while the others worked on driving with the new wheels and drafting the climbing mechanism, i worked on designing our intake and shooter. i made a 3d model and a geometry sketch to figure out how the pivot would work and how our motors would be mounted. i eventually landed on this design that i will edit more tomorrow."}, {"post_id": 30481, "ts": "2026-07-18T02:59:44Z", "hours": 3.7114, "text": "i continued to work on the intake system, which we plan to do a roller and conveyor system to hold/intake the tennis balls. i found several components online and compiled them into a spreadsheet to check the costs. i ran into some issues, especially with figuring out how to pivot the entire conveyor while being able to secure the two plates so they move together."}, {"post_id": 29892, "ts": "2026-07-17T01:52:54Z", "hours": 2.0811, "text": "earlier in the day, we switched out our ESCs so now our wheels can turn in both directions. we also tried flipping over the robot to see if that would allow for a greater chassis to wheel clearance. later, we brainstormed the next steps for our robot, especially the intake and shooter as well as the climb mechanism. i spent a lot of time creating geometry sketches for different ways we can shoot the tennis balls to fit within the size constraints. we ended up choosing to do a conveyor-like system and either magnets or a cascading system for climb."}, {"post_id": 29303, "ts": "2026-07-16T04:03:55Z", "hours": 2.7892, "text": "we assembled all of our aluminum parts using rivets and tested our drive program. our wheels started to fall off because the 3d prints started to get looser on the motors, but we were able to successfully drive. although we only used our unidirectional ESCs, we were able to drive around by only going forward. the bearing pivot had issues as well, especially because when it rammed into cinderblocks it would get bent… but we were able to print containers for our electrical components (batteries, raspberry pi, arducam etc.). i was able to test my vision program to slalom around the cones, and it wasn’t exactly perfect but it was able to respond to obstacles."}, {"post_id": 28762, "ts": "2026-07-15T07:16:14Z", "hours": 5.5689, "text": "after we 3d printed more wheels and glued rubber to the pla, we got to assemble our first iteration of our robot!! we used a cardboard box temporarily, but it was very flimsy so the motors/wheels weren’t very secure. right now, we are using unidirectional ESCs so we will hopefully get bidirectional ones so our robot can go backwards. i used ai to work on a vision program so that the robot would slalom around the red cones using the raspberry pi and arducam. also, our aluminum parts finally came in so tomorrow we will get to put together more parts"}, {"post_id": 26297, "ts": "2026-07-11T00:37:57Z", "hours": 2.5231, "text": "as a team we worked on figuring out the cad for our robot. i worked on making the l-brackets using onshape. we took inspiration from the hammerhead gobilda robot that had the front two wheels on an axle which would help us when navigating the obstacles on the course. i learned how to use new features on onshape, like the sheet metal model and the flange to make bends on the model."}, {"post_id": 25600, "ts": "2026-07-09T23:22:47Z", "hours": 5.0814, "text": "we started brainstorming ideas for how our autonomous robot would look like and function. since our first goal is to just have a robot that can drive, we sketched out a drivebase and chassis. we also researched different materials, like motors, that we may need for the project. earlier, i used ai to develop a vision system on my computer to detect tennis balls."}]...
|
Title: robot - intake and frame by @kaylawong | St Title: robot - intake and frame by @kaylawong | Stardance
Devlogs:
we started brainstorming ideas for how our autonomous robot would look like and function. since our first goal is to just have a robot that can drive, we sketched out a drivebase and chassis. we also researched different materials, like motors, that we may need for the project. earlier, i used ai to develop a vision system on my computer to detect tennis balls.
as a team we worked on figuring out the cad for our robot. i worked on making the l-brackets using onshape. we took inspiration from the hammerhead gobilda robot that had the front two wheels on an axle which would help us when navigating the obstacles on the course. i learned how to use new features on onshape, like the sheet metal model and the flange to make bends on the model.
after we 3d printed more wheels and glued rubber to the pla, we got to assemble our first iteration of our robot!! we used a cardboard box temporarily, but it was very flimsy so the motors/wheels weren’t very secure. right now, we are using unidirectional ESCs so we will hopefully get bidirectional ones so our robot can go backwards. i used ai to work on a vision program so that the robot would slalom around the red cones using the raspberry pi and arducam. also, our aluminum parts finally came in so tomorrow we will get to put together more parts
we assembled all of our aluminum parts using rivets and tested our drive program. our wheels started to fall off because the 3d prints started to get looser on the motors, but we were able to successfully drive. although we only used our unidirectional ESCs, we were able to drive around by only going forward. the bearing pivot had issues as well, especially because when it rammed into cinderblocks it would get bent… but we were able to print containers for our electrical components (batteries, raspberry pi, arducam etc.). i was able to test my vision program to slalom around the cones, and it wasn’t exactly perfect but it was able to respond to obstacles.
earlier in the day, we switched out our ESCs so now our wheels can turn in both directions. we also tried flipping over the robot to see if that would allow for a greater chassis to wheel clearance. later, we brainstormed the next steps for our robot, especially the intake and shooter as well as the climb mechanism. i spent a lot of time creating geometry sketches for different ways we can shoot the tennis balls to fit within the size constraints. we ended up choosing to do a conveyor-like system and either magnets or a cascading system for climb.
i continued to work on the intake system, which we plan to do a roller and conveyor system to hold/intake the tennis balls. i found several components online and compiled them into a spreadsheet to check the costs. i ran into some issues, especially with figuring out how to pivot the entire conveyor while being able to secure the two plates so they move together.
today, my teammates and i first worked on getting the 3d printed center parts of our wheels to fit. the pneumatic wheels were really difficult to work with, especially because the prints weren’t fitting on perfectly (it took us like 1-2 hours). while the others worked on driving with the new wheels and drafting the climbing mechanism, i worked on designing our intake and shooter. i made a 3d model and a geometry sketch to figure out how the pivot would work and how our motors would be mounted. i eventually landed on this design that i will edit more tomorrow.
yesterday i worked on combining our intake system with a pivot. i realized that there was very little space between both motors, so i moved the pivot between where two motors are, where a shaft would be placed under the intake. however, in order to fit under the 8 inch limit, the pvc pipes would have to be smaller or the walls of the intake would have to be shorter. i also started putting other components into the CAD, including the bearings, pipes, and pulleys that might be on the robot. lastly, i tried to modify the bottom pan of the robot’s frame to allow for the intake to turn downwards at a more drastic angle.
i mostly researched different parts to purchase and further refined how the intake would work. to run the conveyor, one motor would power the first roller, then the rest would be connected by pulleys and belts. i also added a motor mount to the side plate of the intake. for the chassis itself, i extended the base plate and L-brackets so that it would be 15x15 inches, which was one of the constraints.
today, i worked on editing our parts that will be machined for our intake. instead of making it three pieces, we realized we could just bend one large piece of metal. for the motor mount, i added a way to allow it to tension so that when we add belts, we can adjust accordingly. i also ordered all the parts we need for the intake system, including the shafts, motor, bearings, etc. lastly, we also started planning out how our shooter would work, which we chose to do two flywheels at the end of the conveyor.
i worked on our shooter design, coming up with different ways to get the tennis balls into a bucket while fitting into our size constraints. especially with having a pivot on our intake, having the motors for the flywheels in the back would add too much weight, so i am still figuring out how to get that to work. i am testing out different orientations for the motors and flywheel, such as a dual horizontal flywheel shooter and a single flywheel shooter.
some of our parts for the intake arrived, so i was able to design more parts of our intake around it. i adjusted the rollers and came up with two different iterations, depending on the width of the rubber we will use to wrap around the rollers. i also measured the diameter of the hex shafts that arrived, which let me test printing out GT2 gears. i edited the gear by finding a part on McMaster-Carr and cutting out the hex shape and chopping off the extruded portion.
here’s an update on what the robot has been doing! i haven’t been working much on the driving part, so my teammates did a majority of figuring out how to navigate the course and how to drive. our robot was able to make it over the hill (but it flipped over if you watch the video lol). i am still working on the intake, but i have gotten further by printing out some pulleys and some shaft to bearing attachments we might use on the robot. tomorrow we will hopefully order sheet metal to hold our intake system. also, one of our prints turned into a blob
today i iterated more on the design of the intake. after printing the pulleys, we realized the belt was slipping and skipping teeth on the pulleys because we were using GT2 belts. we’re now pivoting to HTD-5M belts so we can hopefully not have any slipping happen on our intake. we also ordered more parts and added them to our purchase list!! when the sheet metal comes, we will be able to test the fit of bearings and other parts as well.
yesterday i worked on making the shaft collars and pulleys to print out. our pulley prints were not very successful, but the shaft collars turned out great. i downloaded a free shaft collar file from this link and i changed the size of the screw hole, the width, and i adjusted it so there were two flat ends (so we could use a screw and nut). my team also cut the shafts so we can start making our intake
on friday and today, i mounted the 3d prints to the shafts to start assembling the intake. i was struggling to attach a lot of these parts to the shafts because a layer of the prints kept on stopping the shaft from going all the way through. someone else had to hammer the parts onto the shafts in order for it to work :’) we’re still waiting on our metal part that will contain all of our intake parts, but we do have other components printed. our rollers were printed using TPU, and we also got our pulleys and shaft collars finished as well. we also have very very colorful filament now!!
i worked on assembling the intake, which was mostly the last part of the day today. we got our metal parts in the afternoon ish, so earlier in the day i was working on the guides to funnel the balls into the intake. i also continued refining the cad so now it looks like this!! the intake itself is working decently okay, i have to attach the motor tomorrow. one problem is that i have to wrap around the rubber onto the conveyor, otherwise there is no contact from the rollers on the conveyor to the tennis balls.
i had to take apart and reassemble the intake because the pulleys were on the wrong side… since the pulley on the motor shaft doesn’t fit over the set screw on the shaft, the final belt had to be on the opposite side, so i had to take everything apart :’) my teammate attached the shooter system onto the intake after i was done, so our robot is finally coming together!!
we finally got our intake and shooter to work! i mostly helped by tensioning the neoprene rubber strips and using rubber glue to secure it. earlier, i was trying to make a 3d print to make it easier to cut the rubber strip so that it can be cut at an angle, allowing me to overlap the rubber without making the conveyor too thick. however, that didn’t end up working because the rubber was difficult to cut, so we just decided to glue one on top of the other. one issue we ran into was that on the overlapped portion, it would jam into the pulley for the shooter. this was eventually fixed by cutting the part of the rubber conveyor that was too thick and would hit the pulley! with the extra scraps of rubber that i messed up on, i made a little dude with scissors
i finally have a good idea on how to slice for bambu printers!! i did a lot of 3d printing today, and i started by reprinting the side funnel walls . i also helped to make an attachment so that the limit switch would be pressed once the intake has pivoted past a certain point, to make sure it doesn’t touch the ground. it is definitely not the best solution but i was able to make it work with a bunch of tape and 3d print iterations !!
i just updated our limit switch arm because it ended up snapping!! i thickened the arm and i also included a filleted part to prevent it from snapping so easily. that’s it for today!!
Ships:
Alongside a team of 2 other high schoolers, I helped to make a robot that can intake balls, shoot balls, and navigate across an obstacle course. It was originally intended to be autonomous, but we wanted to focus more on the mechanical systems that were on the robot. I was in charge of designing and assembling the intake subsystem. It was very challenging to fit within the design constraints that we were provided but it was very rewarding once I saw my CAD come to life! This was my first time having something I designed online turn into something physical, so I was very surprised that when all my parts got shipped that it all came together nicely. I did this under a summer program in collaboration with Stardance called Definity Education Summer Camp and it was fun going through trial and error to see what worked and didn’t work. The hardest part was definitely having to make everything from scratch and not buying pre-made FTC or VEX parts, like getting our sheet metal manufactured was a risk in itself! But it was totally worth it....
|
1793252907.4829624
|
1793339307.4829624
|
|
65203
|
robot arm by @chrif | Stardance
|
chrif
|
0
|
8.39
|
0
|
[]
|
[{"post_id": 77130, "ts": "202 [{"post_id": 77130, "ts": "2026-10-05T18:50:54Z", "hours": 5.2839, "text": "i’m almost finished with the design in cad, i already designed all the joints and the servo motor spots but i still havent put down the battery and the microcontrollers and path for the cables, i also started trying to code for the stm32 but i couldnt understand any of the software(cube ide) and i also have been learning how inverse kinematics works off camera just for this project"}, {"post_id": 74962, "ts": "2026-09-28T18:18:43Z", "hours": 3.1061, "text": "I started with mapping out the whole project and I also finished the wiring and the schematics and started experimenting with joints in fusion360 because I want to make the model move in cad (so if you have any tips please let me know in the comments) and i also have been watching videos about reverse kinematics on my own time to figure out how im gonna make the code without relying on ai."}]...
|
Title: robot arm by @chrif | Stardance
Devlogs:
I Title: robot arm by @chrif | Stardance
Devlogs:
I started with mapping out the whole project and I also finished the wiring and the schematics and started experimenting with joints in fusion360 because I want to make the model move in cad (so if you have any tips please let me know in the comments) and i also have been watching videos about reverse kinematics on my own time to figure out how im gonna make the code without relying on ai.
i’m almost finished with the design in cad, i already designed all the joints and the servo motor spots but i still havent put down the battery and the microcontrollers and path for the cables, i also started trying to code for the stm32 but i couldnt understand any of the software(cube ide) and i also have been learning how inverse kinematics works off camera just for this project...
|
1793084088.5574603
|
1793688888.5574603
|
|
50887
|
robot by @sujoypaladhi | Stardance
|
sujoypaladhi
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: robot by @sujoypaladhi | Stardance
|
1793364878.8825123
|
1793969678.8825123
|
|
26467
|
robotic arm by @Amir102 | Stardance
|
Amir102
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: robotic arm by @Amir102 | Stardance
|
1792684467.5089798
|
1793289267.5089798
|
|
59412
|
robotic arm by @andrea_zambolin09 | Stardance
|
andrea_zambolin09
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: robotic arm by @andrea_zambolin09 | Stardan Title: robotic arm by @andrea_zambolin09 | Stardance...
|
1792799336.4549258
|
1793404136.4549258
|
|
48517
|
robotic arm by @himanshujaswal | Stardance
|
himanshujaswal
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: robotic arm by @himanshujaswal | Stardance
|
1793234893.7065399
|
1793839693.7065399
|
|
7542
|
robotic arm by @ianA | Stardance
|
ianA
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: robotic arm by @ianA | Stardance
|
1793235269.6469555
|
1793840069.6469555
|
|
46182
|
robotic arm by @pinkopallo2019 | Stardance
|
pinkopallo2019
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: robotic arm by @pinkopallo2019 | Stardance
|
1792652091.6565843
|
1793256891.6565843
|
|
26367
|
robotic arm by @samiwaseem | Stardance
|
samiwaseem
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: robotic arm by @samiwaseem | Stardance
|
1792803153.4899073
|
1793407953.4899073
|
|
46712
|
robotic arm by @thomas_thomas | Stardance
|
thomas_thomas
|
1
|
27.0
|
0
|
[{"ship_num": 1, "ts": "2026-0 [{"ship_num": 1, "ts": "2026-09-24T16:39:00Z", "hours": 27.0, "multiplier": null, "payout": 0, "blessing": "neutral", "text": "It’s a completely costum 3 degrees of freedom robotic arm. It is based on an Arduino and can be controlled from your PC. I designed every single part of it myself, even designing the cycloidal drives myself. Especially the electronics were hard and often made me lose motivation (and money). But the cycloidal drive design wasnt easy as well at first. I learned many crucial skills (with this being my first electronics project): -Soldering -Proper 3d Printing and assembly oriented CAD design -How Stepper motors, Electrical circuits and many tools actually work -Documenting your work -Intuition as to what might work and what wont So may more things! This was my entry project into the world of engineering and I have so many more prejcts in mind that I would like to build someday.", "repo": "https://github.com/pommjes/robotic-arm", "demo": ""}]...
|
[{"post_id": 73168, "ts": "202 [{"post_id": 73168, "ts": "2026-09-24T15:01:07Z", "hours": 0.6667, "text": "Working on the finishing touches rn. Just made this baseplate, which I dont have myself since I made mine out of a piece of wood. Its much easier that way but in case anyone doesnt have any wood or no tools for drilling etc I made this baseplate for them."}, {"post_id": 70714, "ts": "2026-09-19T17:15:57Z", "hours": 2.9667, "text": "This is getting pretty hard now… After basically finishing the rough hardware I started finishing the software. Added a zero function which I have to use for now since the homing isnt accurate at all yet and you basically always have to home it manually (using a level lol). But the main issue is the target point function since the arm seems to make up some random data and drifts like 20(!!) cm from the target point while being spot on on the other axis. But I’ll get there in the next few days… And guess what youre getting another screenshot bcs the real arm still looks like shit:"}, {"post_id": 69718, "ts": "2026-09-17T17:17:54Z", "hours": 1.5664, "text": "Finally installed a Nema 17 as the base motor for the arm. The old Nema 14 often struggled to move the correct number of steps due to too much load. The old one had 12Ncm while the new one has 45Ncm :D. I obviously dont have anymore problems with the base motor missing steps… Also the new base with the integrated magnet holder and bearing ring is way more practical than the old design. You wont notice a difference visually and its ugly af with all the cables so yall are getting another rendering:"}, {"post_id": 66780, "ts": "2026-09-12T17:45:13Z", "hours": 1.0597, "text": "Put all the mounts for the magnets and hall effect sensors on the main parts so less printing and no glueing is needed. Posting devlogs more frequently now"}, {"post_id": 66514, "ts": "2026-09-12T11:04:53Z", "hours": 2.0667, "text": "Im going to upgrade the stepper of joint 1 from a nema14 to a nema17. The nema14 just didnt have enough torque to spin the arm without shaking a lot and skipping like half of the steps… This is the new motor case for mounting it. It will now have the lower bearing cap attached to it for easyer printing and assembly. Also finished installing and connecting all the hall effect sensors to the arm and every joint is homeable now! Next will be installing the new motor and then calibrating the software and if everything goes according to plan (it wont) i’ll be done!!"}, {"post_id": 66030, "ts": "2026-09-11T15:56:14Z", "hours": 0.7333, "text": "Did a cfd analysis of the housing for my arduino and the cnc shield. It was pretty hard to figure out but also very fun and most of all a valuable skill to learn. This might be a bit overkill but it actually helped me improve the case for better airflow over the heatsinks of the motor drivers to ensure keeping them cool for when the arm will be working for longer periods of time and under higher loads. For anyone interested I did this in Simscale its very easy to use and you get 10 free simulations!"}, {"post_id": 65026, "ts": "2026-09-09T20:36:09Z", "hours": 10.3181, "text": "Worked a lot on the homing of my arm. Did a lot of software first off. Adding the homing buttons to the GUI, adding the commands which will be send to the arduino and adding the homing feature on the arduino itself were a lot of work but we got there in the end! Also added a section for error messages and stuff at the bottom. For the hardware side I glued all the hall effect sensor in place along with the magnets needed for the sensors. I glued them on but i plan on putting designated spots on the arm for the magnets and sensors so you wont have to glue them on if you decide to build the arm yourself. I then had to build a small board to allow all the sensors to recieve power from the cnc shield. I now also started connecting all the sensors to the arduino which takes a lot longer than expected actually. But ive been successful so far and both of the connected joints work as intended right now. Next up i have to finish connecting everything and then finish so software side so the arms get to the correct position once they’re homed. This is a lot for one devlog but its hard to define clear borders in this project since everything is connected with everything else so you are never “done” with one subproject because its only a small part and wont work without me working on the next thing. Also didnt add any pics of the arm with the sensors since you cant really notice a difference compared to the older photo on here except a few more wires…"}, {"post_id": 53970, "ts": "2026-08-24T17:58:02Z", "hours": 1.8, "text": "I cant belive I’m typing this, but it works. After intensively working on the software side the last few days while waiting for the last hardware pieces needed I assembled everything today. I put on the jumper caps to enable microstepping and installed the new cables for joint 3. When everything was done i just had to do a tiny bit of troubleshooting but after that it just worked! I can control all the joints and the software also doesnt really have any issues. The only thing that doesn’t fully work yet is the target point feature, since i cant home the arm yet. Some magnets will arrive in a few days and i’ll use them with hall effect sensors to home the arm. Then it’ll hopfuly work just as it should and i can get to documenting this whole thing for you guys!"}, {"post_id": 52250, "ts": "2026-08-22T10:52:49Z", "hours": 6.0089, "text": "Advanced the software side quite a bit. Waiting for some new parts rn so I used the time to finish the gui for now and calculate everything i need to to send to the arduino! You can now input the three values making up the target point and hit calculate. Then the angles of the three joints relative to their home position as well as the delta of the angles, which is the value which the joints need to move in order to reach the target position, are displayed below. These are the values which are going to be send to the arduino. I also added the function to ignore physically impossible positions of the arm and only show the possible ones. In theory I only have to add some kind of interface to send data to the arduino and the software would be done!"}, {"post_id": 45313, "ts": "2026-08-11T17:59:37Z", "hours": 1.41, "text": "Wrote the code to controll all 3 joints.did this to prepare and pass time while i wait for my cnc shield. once it arrives ill hopefully be able to fully control my arm for the first time ever! No new features (apart from the 3rd joint) where added, but i cleaned up the code from the last version a bit and removed some unused sections. I also added lots of comments to make editing and extending the code easier in the future."}, {"post_id": 45139, "ts": "2026-08-11T13:14:44Z", "hours": 0.3, "text": "New Joint 1 -> Base interface without wobbling like the one before."}]...
|
Title: robotic arm by @thomas_thomas | Stardance
Title: robotic arm by @thomas_thomas | Stardance
Devlogs:
New Joint 1 -> Base interface without wobbling like the one before.
Wrote the code to controll all 3 joints.did this to prepare and pass time while i wait for my cnc shield. once it arrives ill hopefully be able to fully control my arm for the first time ever! No new features (apart from the 3rd joint) where added, but i cleaned up the code from the last version a bit and removed some unused sections. I also added lots of comments to make editing and extending the code easier in the future.
Advanced the software side quite a bit. Waiting for some new parts rn so I used the time to finish the gui for now and calculate everything i need to to send to the arduino! You can now input the three values making up the target point and hit calculate. Then the angles of the three joints relative to their home position as well as the delta of the angles, which is the value which the joints need to move in order to reach the target position, are displayed below. These are the values which are going to be send to the arduino. I also added the function to ignore physically impossible positions of the arm and only show the possible ones. In theory I only have to add some kind of interface to send data to the arduino and the software would be done!
I cant belive I’m typing this, but it works. After intensively working on the software side the last few days while waiting for the last hardware pieces needed I assembled everything today. I put on the jumper caps to enable microstepping and installed the new cables for joint 3. When everything was done i just had to do a tiny bit of troubleshooting but after that it just worked! I can control all the joints and the software also doesnt really have any issues. The only thing that doesn’t fully work yet is the target point feature, since i cant home the arm yet. Some magnets will arrive in a few days and i’ll use them with hall effect sensors to home the arm. Then it’ll hopfuly work just as it should and i can get to documenting this whole thing for you guys!
Worked a lot on the homing of my arm. Did a lot of software first off. Adding the homing buttons to the GUI, adding the commands which will be send to the arduino and adding the homing feature on the arduino itself were a lot of work but we got there in the end! Also added a section for error messages and stuff at the bottom. For the hardware side I glued all the hall effect sensor in place along with the magnets needed for the sensors. I glued them on but i plan on putting designated spots on the arm for the magnets and sensors so you wont have to glue them on if you decide to build the arm yourself. I then had to build a small board to allow all the sensors to recieve power from the cnc shield. I now also started connecting all the sensors to the arduino which takes a lot longer than expected actually. But ive been successful so far and both of the connected joints work as intended right now. Next up i have to finish connecting everything and then finish so software side so the arms get to the correct position once they’re homed. This is a lot for one devlog but its hard to define clear borders in this project since everything is connected with everything else so you are never “done” with one subproject because its only a small part and wont work without me working on the next thing. Also didnt add any pics of the arm with the sensors since you cant really notice a difference compared to the older photo on here except a few more wires…
Did a cfd analysis of the housing for my arduino and the cnc shield. It was pretty hard to figure out but also very fun and most of all a valuable skill to learn. This might be a bit overkill but it actually helped me improve the case for better airflow over the heatsinks of the motor drivers to ensure keeping them cool for when the arm will be working for longer periods of time and under higher loads. For anyone interested I did this in Simscale its very easy to use and you get 10 free simulations!
Im going to upgrade the stepper of joint 1 from a nema14 to a nema17. The nema14 just didnt have enough torque to spin the arm without shaking a lot and skipping like half of the steps… This is the new motor case for mounting it. It will now have the lower bearing cap attached to it for easyer printing and assembly. Also finished installing and connecting all the hall effect sensors to the arm and every joint is homeable now! Next will be installing the new motor and then calibrating the software and if everything goes according to plan (it wont) i’ll be done!!
Put all the mounts for the magnets and hall effect sensors on the main parts so less printing and no glueing is needed. Posting devlogs more frequently now
Finally installed a Nema 17 as the base motor for the arm. The old Nema 14 often struggled to move the correct number of steps due to too much load. The old one had 12Ncm while the new one has 45Ncm :D. I obviously dont have anymore problems with the base motor missing steps… Also the new base with the integrated magnet holder and bearing ring is way more practical than the old design. You wont notice a difference visually and its ugly af with all the cables so yall are getting another rendering:
This is getting pretty hard now… After basically finishing the rough hardware I started finishing the software. Added a zero function which I have to use for now since the homing isnt accurate at all yet and you basically always have to home it manually (using a level lol). But the main issue is the target point function since the arm seems to make up some random data and drifts like 20(!!) cm from the target point while being spot on on the other axis. But I’ll get there in the next few days… And guess what youre getting another screenshot bcs the real arm still looks like shit:
Working on the finishing touches rn. Just made this baseplate, which I dont have myself since I made mine out of a piece of wood. Its much easier that way but in case anyone doesnt have any wood or no tools for drilling etc I made this baseplate for them.
Ships:
It’s a completely costum 3 degrees of freedom robotic arm. It is based on an Arduino and can be controlled from your PC. I designed every single part of it myself, even designing the cycloidal drives myself. Especially the electronics were hard and often made me lose motivation (and money). But the cycloidal drive design wasnt easy as well at first. I learned many crucial skills (with this being my first electronics project): -Soldering -Proper 3d Printing and assembly oriented CAD design -How Stepper motors, Electrical circuits and many tools actually work -Documenting your work -Intuition as to what might work and what wont So may more things! This was my entry project into the world of engineering and I have so many more prejcts in mind that I would like to build someday....
|
1793335963.2577379
|
1793422363.2577379
|
|
60396
|
robotic arm controlled by bluetooth by @originalbr robotic arm controlled by bluetooth by @originalbranko | Stardance...
|
originalbranko
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: robotic arm controlled by bluetooth by @ori Title: robotic arm controlled by bluetooth by @originalbranko | Stardance...
|
1793033467.5776517
|
1793638267.5776517
|
|
45982
|
robotic hand by @advait_padalkar | Stardance
|
advait_padalkar
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: robotic hand by @advait_padalkar | Stardanc Title: robotic hand by @advait_padalkar | Stardance...
|
1793364517.5568779
|
1793969317.5568779
|
|
60218
|
robotic hand by @amjad | Stardance
|
amjad
|
0
|
0.0
|
0
|
[]
|
[]
|
Title: robotic hand by @amjad | Stardance
|
1793033335.4804873
|
1793638135.4804873
|
|
47871
|
robotic hand by @mohak_gupta147 | Stardance
|
mohak_gupta147
|
0
|
5.87
|
0
|
[]
|
[{"post_id": 70828, "ts": "202 [{"post_id": 70828, "ts": "2026-09-19T20:44:05Z", "hours": 3.5358, "text": "finished half of the index finger design"}, {"post_id": 70603, "ts": "2026-09-19T13:11:36Z", "hours": 0.9667, "text": "started designing the hand"}, {"post_id": 55880, "ts": "2026-08-27T19:44:03Z", "hours": 0.4833, "text": "connected the n20 motor to its driver then to the pca9685 servo driver technically making it a servo and use less gpio pins (gives more room to expand) when more n20 mototrs would be added"}, {"post_id": 55764, "ts": "2026-08-27T19:09:51Z", "hours": 0.8833, "text": "wired the motrs for the hand actuators"}]...
|
Title: robotic hand by @mohak_gupta147 | Stardance Title: robotic hand by @mohak_gupta147 | Stardance
Devlogs:
wired the motrs for the hand actuators
connected the n20 motor to its driver then to the pca9685 servo driver technically making it a servo and use less gpio pins (gives more room to expand) when more n20 mototrs would be added
started designing the hand
finished half of the index finger design...
|
1793199172.956441
|
1793803972.956441
|