Before we get into it, I'll keep this short and sweet.
I am looking for work.
I'm a software developer specializing in backend development, infrastructure, and devops.
I'm also interested in and have explored embedded firmware, rendering, and electrical engineering.
Three things I really care about are reproducibility, documentation, and community.
Take a look at my resume if you're hiring - or even if you're not!
Heck, send it to your friends.
contact me: dwbrite at gmail dot com
At the end of 2022 I was hosting my website on Linode Kubernetes Engine.
My monthly spend reached $48/mo for a slightly excessive setup - but, in fairness,
I was doing a lot of experimentation on Linode.
I had just lost my job at Remediant[0]
and this monthly subscription was enough to make me a little uncomfortable while unemployed, so I set a new goal:
Build my own kubernetes cluster that's stronger and cheaper,
so that I could still run my website (and various other servers),
should finding a job take too long.
step 1: hardware
The Orange Pi 5 came out less than two months before I decided to build my own cluster.
It's an 8 core little beast
with 8GB RAM for $90 MSRP, at a time when the Raspberry Pi 4B was going for $200, if you could even find it.
On the surface, OS support seemed pretty good.
It was an obvious choice, if a bit overkill - being 3-4x stronger than the RPi.
I bought 3 with an estimated ROI period of just under a year -
not including the benefits of far more capable hardware than I could find from VPS providers.
step 2: networking
At my apartment in NYC I was behind CGNAT[1] with NYC Mesh, and IPv6 support was (is) nonexistent.
That left me with two options:
Giving a third party unencrypted access to all traffic flowing into and out of my cluster (see: Cloudflare Tunnels), or
Hosting at my mom's house.
I opted for the latter (thanks mom), which meant progress slowed significantly until I moved back in with her.
When I got there, I upgraded our firewall/router to a mini PC running VyOS.
This allowed me to define my firewall's configuration as code,
upload it with Ansible, and not have to manually dig around in some UI for each change.
It's similar to Ubiquiti's EdgeOS and Juniper's Junos OS in that way.
I find it incredibly comforting that my network configuration is easy to reproduce or roll back.
step 3: building the cluster
Before I could think about Kubernetes, I needed an OS to run it on.
And before that, I needed to be able to boot from the NVMe drive, which the Orange Pi 5 does not support out of the box.
Fortunately the process to boot from NVMe is tolerable enough -
just load up an official Orange Pi distro and update the SPI_FLASH bootloader via orangepi-config.
Once I did that, I installed RebornOS on a USB and wrote a makefile to do some initial config and copy the install to each machine's NVMe drive.
I chose RebornOS because it appeared to be better supported than other distributions.
And honestly, the official Orange Pi distros seemed kind of sketchy[2].
I opted for k0s as my kubernetes distribution, because it's ridiculously easy to install and
it allows me to declaritively define my cluster's topology.
I was also already familiar with Mirantis because of Lens, which certainly helped.
Small hiccup: while k0s does support ARM64, the k0s controller can't run on ARM because of some issue with etcd.
I didn't look much into it, but...:
Poor man's solution: run the k0s controller on my x86 firewall, and remember to never ever ever (ever) open port 6443.
It took less than an hour to get a proper kubernetes cluster running.
step 4: system applications
Having worked with kubernetes only in the context of cloud providers,
I expected many things to just work out of the box.
To test the cluster I attempted to deploy a basic web server with nginx-ingress-controller and cert-manager.
I found out pretty quickly that if I wanted to create a LoadBalancer Service, I'd need a load balancer.
On a whim, I installed MetalLB and it worked with minimal configuration.
Just as well, I now had a discrete pool of IP addresses I could port forward to.
Then I decided, fuck it, let's learn Istio, and I replaced nginx-ingress-controller.
The switch was surprisingly easy, and I'd say it feels slightly cleaner overall.
I also installed ArgoCD so I could manage the applications running on my cluster
without having to rely strictly on helm.
This has the added benefit that I don't have to worry as much about my deployments,
especially when resources deploy out of order.
step 5: continuous integration and pain
After I demo'd Istio and friends, I wanted to get my real website up.
To do that, I needed to build ARM container images for it.
On Github Actions, this took over an hour.
But I'm impatient, and I had some very capable little ARM machines within walking distance.
So, I deployed the github actions runner controller, to control deployment of github actions self-hosted runners. :^)
This was the first time anything on my cluster needed wanted to create persistent volumes,
and apparently I did not have a storage provisioner.
*sigh*
So, I installed Ceph+Rook.
My memory of that ordeal is entirely gone, but suffice to say something didn't work,
and configuration was a bit painful. Then reddit said Longhorn should be simpler, so I pivoted to that.
I installed it with ArgoCD and helm, and...
Turns out RebornOS for the Orange Pi 5 doesn't have iSCSI kernel modules.
But that's fine I guess. It had been several months since I installed RebornOS,
and Joshua Riek's ubuntu-rockchip distro
was really picking up steam.
I installed ubuntu-rockchip on one machine and gave Longhorn another go.
I set every nodeSelector I could find in Helm to target that machine, but alas-
So I set some taints and tolerations, even though I would have really preferred to stick with nodeSelectors.
... š
So I started from scratch with OpenEBS.
Mayastor seemed to be the best storage engine for OpenEBS, and it looked easy to configure,
and it's written in Rust. So it had to be a good choice.
There was just one more issue.
Mayastor relies on nvme_fabric, which is not enabled by default in the linux-rockchip kernel.
So I enabled it, compiled ubuntu-rockchip myself, and finally got persistent volumes working.
If you're looking to reproduce this, you can compile ubuntu-rockchip yourself, or use the image I built.
Funny story though, you can actually disable the volume claims on ARC, so none of this was really necessary at the time.
But, once I start running my plex ser--
Alright then, once I start running Jellyfin on the cluster, and Outline, I'll be happy I did all that.
step 6: blue/green deployments with istio and argocd
Back to happy boring land,
I created two ArgoCD Applications which point to blue and green
Kustomize overlays for my website's Deployment.
Each overlay points to a specific version/tag[4] of my website's container image, and labels the resources.
All I need to do to switch which one is live is modify the VirtualService and push my changes.
It's downright pleasant.
now and onwards, and other random thoughts
As painful as some of this was, I'm very happy with the outcome, and it made an excellent learning experience.
I know a lot of people dislike kubernetes for being over-complicated, but once it's configured
it makes deploying new applications ridiculously easy.
And, I love when my projects are declaritive, automated, and reproducible. This has given me all of that.
If you like the way I think, please hire me!
As noted, I've been out of work for 9 months.
Finding work in today's market has been extraordinarily tough.
Like, 0.5% response rate compared to 9% last year tough.
I have a number of referrals available upon request, and one testimonial I'm particularly fond of.
Closing out after 7 months working for Remediant, a senior developer said to me:
"[I] think you've progressed well beyond junior devops at this point which is a testament to your ability and drive."
Thanks Scott :)
If you're looking for a backend engineer with DevOps chops, or junior-(ish?) DevOps engineer,
please take a look at my resume and we can schedule a call.
Onwards, for my cluster I have five... six... uh, -- many projects in mind:
My non-tech friend wants me to host her website; just a simple wordpress site
Jellyfin, and Some service to upload - or download ;) - media onto Jellyfin's data volume
Outline Wiki, to store my thoughts, ideas, and collections
Keycloak/Authelia/Authentik for authentication into cluster-hosted services[5]
3-2-1 Backups.
Maybe putting an old laptop in the cluster to get x86 support where absolutely needed. Plex, I'm looking at you.
I'd really like to do something with the GPU compute available on the Orange Pi 5.
At some point (maybe once some of these projects are finished)
I'd like to fork this project and turn it into an afforable and easily reproducible
"infrastructure blueprint", a la hackerspace.zone.
In other news, it seems support for the
Orange Pi 5 in mainline linux
is on its way. Currently, all Orange Pi 5 distros are based on Rockchip's custom 5.10 kernel,
which as I recall isn't even really 5.10 in the first place.
The total cost for my cluster was just under $500.
That's about $165 including a power supply and 256GB NVMe drive for each machine, plus tax.
It's a pretty steep up-front cost, and honestly one machine would have probably sufficed -
but then I wouldn't be able to play with DaemonSets or cordon my nodes to update their OS without losing a beat!
Not mentioned earlier, is that I accidentally fried one in a freak wiring incident
while attempting to access the serial interface, because I couldn't find my jumper wires. Oops.
The amortized recurring cost of the cluster, given infinite time, is something like $4/mo. But that's only because...
I'm not exposing my home's IP address to the world!
I have an nginx proxy on a cheap VPS for about $3.50/mo, and update my home's IP address in that with a
simple script.
Fun fact: Chick-fil-A is notorious for running bare-metal kubernetes clusters
at each of their restaurants
with consumer hardware! I just think this is a really neat idea and a fun piece of kubernetes lore :)
[0] In December 2022, Netwrix acquired Remediant, and dropped the entire SaaS product and team. This included myself.
[1] It's actually possible to get a public IPv4 address assigned with NYC Mesh, but it involves talking to people, and I had the intuition I'd be moving in a few months anyway.
[2] Well, there's a script that runs on startup which curls baidu to see if the internet is working. Not incriminating, but not inspiring either.
[3] IIRC it was the longhorn-engine daemonset I couldn't set the nodeSelector on, and which I
could not
find a
source for.
The docs did not help.
In retrospect, I probably could have found a solution by digging a little deeper -
perhaps by looking at the manifests generated in ArgoCD.
Alas, I am human with limits on frustration and hope, and OpenEBS was starting to look very compelling.
[4]see: overlays and routing. This works well enough for blue/green. I'd like to add a few features here, like maybe having blue and green.dwbrite.com so I can see changes before putting them live. Maybe those could only be accessible from LAN (via VPN?). I'd also like to get some canarying/automated deployment going. I have Flagger in mind for this.
[5] Outline, Matrix, maybe Jellyfin. I'm not sure what else I'll host.
[6] Since discovering that the domain ns.agency was available, and then purchasing it, I have become slightly obsessed with this idea of hosting a public nameserver. Public utilities like this are so enchanting to me. If you haven't heart of sdf.org, I recommend checking it out.
Ever since I started learning Rust about 4 years ago, I've been in love with its enums.
You see, Rust's enums aren't strictly enumerations.
They're closer to tagged unions or sum types, which are used to represent variants.
Let's take a look at what an enum is, and a few cool use-cases for them.
Rust's enums are something of a mix between the two.
You can read up on the basics in
the book and/or
Rust by Example.
That said, let's look at a simple enum in Rust and how it might be implemented in C.
Note that Rust's enums share the same span of memory between its constituent types,
so only a small amount of memory is wasted.
You can play around with their internal representations in
this playground,
or read more in the nomicon.
error handling with variants
Two particularly useful enums are Result and Option.
Result gives you a way to represent whether an operation is successful or not,
as well as a way to access the data or error of the... result. š
Option gives you a way to represent whether something exists or not.
This is generally used as a replacement for nullable types
(which Rust does not have*).
But what really makes Rust shine is that it forces you to explicitly handle enum variants before you can access the underlying data.
This is done using the match keyword.
Rust also has special syntax
for handling Results and Options when you raise issues from the unhappy path.
To start, let's compare how we handle a simple http endpoint with Go's gorilla/mux and Rust's Rocket.
Rust:
Go:
By having data embedded into variants, we can represent whether an operation is successful or not.
Potential errors can be propagated, transformed, and handled without mucking up your happy path.
Very cool. š
Let's go deeper.
heap allocation and dynamic dispatch
Imagine you're writing an audio system for a game.
You have a directed acyclic graph and you need a way to represent the nodes in this graph.
A node can be an input (sine wave, mp3), effect (pan, mix), or output (speakers, a file, visualizer).
What all nodes have in common is one function: process(inputs, outputs).
Let's call this common behaviour the AudioNode interface (or trait).
So our audio graph looks something like Graph<AudioNode>.
In practice then, each node in the graph is dynamically sized and must be heap allocated.
To perform that heap allocation in Rust, our nodes must be wrapped in a smart pointer:
Box<dyn AudioNode>.
All this results in significant overhead with multiple vtable accesses, and more importantly:
indirection which prevents compiler optimization.
Keep in mind process(..) is called multiple thousands of times per second.
But we can improve that with enums:
In my project I'm getting up to 10% better performance.
Not at all laughable in audio programming.
You can remove a lot of boilerplate here with the impl-enum or enum_dispatch crates
(see enum_dispatch benchmarks).
Very, very cool. š
message passing
Imagine you're writing a music player.
Your UI has controls for play/pause, seek, skip, etc.
These inputs can come from different places - like
dbus,
hotkeys, or simple UI interactions.
We've just run into an ideal use-case for MPSC (multi-producer/single-consumer) channels!
An MPSC channel is simply an atomic queue
that can only be accessed through its producers and consumers.
Whenever one of the aforementioned controls are triggered,
we can send a message through an MPSC channel to control playback.
With that out of the way, we need to determine what data to send.
Let's look at some potential Java-esque solutions.
Normal enums won't work because some of our controls like Seek(timestamp) have associated data.
Maybe a class with an enum field, plus fields for each type of associated data would work?
Or a string?
It's an oddly gnarly problem to solve.
Fortunately for us, Rust's enums make this easy.
This is part of what makes multithreading so nice in Rust.
closing thoughts
The last pattern using enums that I'd like to shine some light on is the finite state machine.
Plenty of others have written about state machines in Rust before,
so I won't reiterate on that.
I'm a generalist software developer with a specialization in backend / web architecture.
I've spent the last nine years honing the craft in my free time, and I'd really like to get my foot in the door professionally.
Send me an email if you know of any internships, contract positions, or full-time employment that I might be a good fit for!
Long ago I went to a 48 hour game jam at Becker University.
It was the global game jam and the theme was what do we do now?
At the time I had already been working on a 2D game project on top of a javafx canvas,
so I copied the text rendering code and said let's make a text adventure!
It was a fun little project, but little did we know the dangers of such a thing.
You see, we were in ye olde java 7 times[1],
fresh out of our second year of computer science in high school.
We had even struggled to represent branching trees of text that could loop back upon themselves. Perhaps better known as directed graphs.
Text was represented as strings with special tokens to change how fast text was printed.
For example, "\^" meant the following text should print quickly, and "\#" meant that text should print slow.
This has made many people very angry and has been widely regarded as a bad move.
A few years later, I tried to run the game out on Linux, and, surprise! š
Audio doesn't play, the logic thread crashes and burns, and you're left with an unresponsive window.
Write once, run anywhere, eh?[2]
So 6 years after that, I finally resolved to finish that game the right way.
The core idea of void is to have
text that is engaging. Sometimes you want
texttowiggle,
or type-write
slowly,
or any other of the infinite possibilities to add character to text.
Which makes any markup language a natural choice for text representation.
And with that I've started using xml to represent my game text.
This required parsing my xml and turning it into data structures stored in bincode files for later use.
The last part to navigate then, is branching storylines.
I initially decided that this would also be done in xml,
so long as the logic isn't much more complicated than checking booleans -
but part of me is thinking that maybe these logic checks should be done in Rust.
Anyway, that's all for today! Hopefully I won't get too distracted with other projects in the near future
Technically java 8 had just come out the year before, but we were inexperienced -
we didn't even know what a lambda was, let alone how to use it.
Frankly, I even thought writing code in a legacy style was considered good practice because it was
backwards compatible š¤¦
As long as you're not on linux.
And while we're at it, even if you successfully create a cross-platform abstraction layer,
you'd need to pack all the abstractions into one distributable. Or create multiple distributables.
i've fallen into a pattern of metaphorically losing my mind,
regaining it again, and then forgetting -
returning to a state of mere existence for months at a time.
you go from thought - and i mean real, genuine thought - ...
you go from genuine thought, and you become just a process.
you may still problem solve, interact with others, et cetera, et al,
but you live on autopilot.
ask yourself: how often do you think about what you're actually doing?
where do you want to go?
how are you going to get there?
what are you actually doing?
we live on some dumb rock flying through space.
we're humans. we live because we fuck.
--- stop and acknowledge that. ---
yet at least half of our waking lives (at work, university), procreation is unspeakable.
...
and that's funny. maybe a little sad.
but that's not the point.
the point is that you have to put on a facade, day in and day out.
whether your facade is in regards to sex
or you're embarrassed about your immense plant collection.
you exist almost independent from your true self.
you think differently,
you behave differently,
you live on autopilot.
but sometimes, for a little while, you break out.
this is a reminder to wake up.
smell the flowers.
get back to doing what you want to do.
take that trip.
get that project done.
tell her how you really feel. take off that facade.
Nearly five years ago I finished a lightboard project consisting of
a WS2812 LED strip, a sheet of plexiglas, and an
adafruit trinket.
I never did write a blog post about it,
but now is as good a time as ever.
Originally, the 48"x24"x1/8" acrylic sheet was
mounted with a 1/4" wide plastic J channel screwed into drywall.
The top of the sheet was kept in place with a nail at each corner,
allowing the acrylic to slide out easily for maintenance.
This mounting solution was not quite good enough,
and daily use wore down the drywall until the board could no longer stand.
A few years later, I resolved to make a better lightboard,
an advanced lightboard!
I built plywood shelves for an unrelated project,
and used that to mount the next revision of the lightboard.
The new revision uses side-mounted WS2812 LEDs,
which can fit perfectly in a 1/8" aluminum J channel.
[Could not load media at startup. The media server may be down.]
With the mounting hardware set up, it was time to work on the electronics.
I had spent a good chunk of my free time in the past year writing
firmware
in Rust for the
teensy 3.2.
I hoped I could use this firmware to build the lightboard's interface,
so I added SPI functionality to the teensy, and... it wasn't fast enough!
Turns out, assembly is essential for firmware development.
And I really did not feel like learning assembly at the time.
So, I resigned myself to learning C++ and writing
the software
in that with the help of Arduino libraries.
(holy heck, this is a long post, sorry)
Onto the interface!
The plan is to use three rotary encoders and a small OLED screen.
Then I'll plop these into a 3D printed enclosure (which I'll figure out later).
For now, I've mounted the prototype to some cardboard!~
[Could not load media at startup. The media server may be down.]
[Could not load media at startup. The media server may be down.]
The three rotary encoders will control things like the
"mode" of the lightboard - on, off, rainbow cycle, etc -
parameters of different modes, and resets for those parameters.
I haven't perfectly defined the interface yet, but I have plenty of options.
Right now I'm thinking each mode will have three parameters,
and the mode can be changed by depressing the first knob while turning.
I'm going to end this post here and leave the software for another post.
But first... A celebration!
I've recently graduated with an A.S. in Computer Information Systems,
and I've been accepted into UMass Amherst for a B.S. in Mathematics! š
I'm still not sure about what I'll do in the future,
but at least I've accomplished a few things, eh?
Wow. I canāt believe itās been two years since my last blog post.
Iāve learned a lot since the start of this blog. Well, here I am.
I read a bunch of books on programming. I got my diploma and started working on a degree.
Man, how time flies.
Iām trying to be more conscious of the world around me.
Iām using Linux whenever possible, but some classes require otherwise.
Luckily spinning up a VM is never too hard.
Iāve tested out every desktop environment under the sea before finally settling on Gnome.
I still canāt find a suitable terminal or file manager.
These are two things that macOS seem to get right, but which no-one else can.
Ligatures! I love them. The mozilla foundation is a developerās best friend.
FiraMono/FiraCode. Rust. Firefox nightly. Just... Everything.
Theyāre so great right now! Iām glad all their efforts are catching up to them :)
Iāve also learned a bit of OpenGL.
I bought a domain and made a website
which blew up on reddit for a minute. I started learning Rust and Kotlin.
The Pokemon project is still ongoing!
Itās currently in a private github repository,
and I think Iāll release it when the code is pretty enough.
Iām refactoring the old code, updating it with new knowledge,
and replacing Java with Kotlin as I go.
Planning for battles - the largest part of development - is nearly complete.
Everything else should move along pretty quickly,
and progress should look much faster when that happens.
Iām sure Iām forgetting some things, but this will do for now.
Iāve been yelling at myself to write something for a while now,
but Iāve been putting it off because I havenāt really figured out what to write.
So I guess Iāll just wing it and this will be whatever it is.
Over the past however-long-itās-been-since-my-last-post I havenāt been doing nothing.
I mean Iāve mostly been doing nothing, but I havenāt been doing completely nothing.
When I reached the point in my Pokemon remake where I had to
gather all of the data for the Pokemon I got lazy.
I really donāt like tedious work, so I kind of ignored it for a while.
In the mean time I did a few other things to kill time.
The very first thing I made since Iāve been gone is a glowey-whiteboard.
Hereās the final result:
[Could not load media at startup. The media server may be down.]
[Could not load media at startup. The media server may be down.]
The whole build needs a post of its own so Iāll try to do that within the next week.
After that I got back on track a bit.
I downloaded a bunch of sprites from
Veekunās collection of downloads.
I also wanted to get the animated sprites from Black/White,
so I used HTTP GET requests to gather those from Pokemondb.
It took a while and it ended up being something like 65MB total.
Not terrible, but I can understand why Veekun wouldnāt want that on his server.
At around the same time I finally decided to get all of the Pokemon data up until gen 3.
Basically I made a list of all the data needed for each Pokemon
(Index #s, names, gender rates, color, egg types, etc.), and put it all into one convenient
spreadsheet.
I have some different pages for certain data like moves, evolutions, and forms -
but I havenāt filled out forms and moves yet.
I still have to decide how Iām going to program those in first.
For forms/transformation Iāve been tossing around this data structure that seems to fit how GameFreak does it.
There are three form interfaces: āIn-battle Transformingā (Castform, Cherrim, Giratina),
āEvolution Transformingā (Deoxys, Burmy, Rotom), āNon-Transformingā (Unown, Shellos).
Thereās probably some worksheet pages that I used just to paste some regex data, so you can ignore those.
I also added the particles for ledge-jumping and running through grass,
and Iām in the process of adding battles right now,
I just need to figure out how Iām going to implement it.
Hereās a beautifully looped gif of the game in its current form.
[Could not load media at startup. The media server may be down.]
I also painted and reorganized my room, and added some neat lights,
which Iāll show sometime in the future whenever I get a chance.
Thatās about it for the past. Now onto the future.
My current non-Pokemon projects are making a smart mirror
from an old laptop screen and some minicomputer, and making a custom aux cable.
The smart mirror is fairly straightforward,
but the aux cable is where it gets interesting.
Iāve always been frustrated by headphones.
Especially expensive ones with hardware you canāt replace.
The most notably example being the cord, as simple as it is.
They always seem to break, usually right at the base of the jack,
completely ruining a nice pair of headphones.
The solution is usually to cut it off and solder on a new jack,
which shortens the cord and usually comes out pretty ugly.
Another thing that bothers me is the length of the wire.
Theyāre either too short for anything but tying an iPod to your arm,
or so long that they dangle down to your knees.
I might be exaggerating a bit, but it begs the question:
why arenāt headphone wires made to be retractable? Obviously not buds though.
So Iāve decided Iām going to
a.) mount a female TRS jack where the wire splits, and
b.) make a custom TRS/aux cable.
As a personal choice Iād like to have it with a right-angle jack on one end,
a straight jack on the other, and a paracord sheath.
I also want it to have a retractable coil.
This can all be done by Pexon,
but itās cheaper to do on my own and should offer some valuable experience.
The idea is to get some magnet wire, twist those together,
wrap that in aluminum coated Mylar to prevent interference,
cover that in a polyvinyl sleeve, then cover that in a Nylon paracord sheath,
wrap the cable around a metal rod and bake it hot enough for heat memory,
then twist it in the opposite direction of the coil to make it snappy, and put on the jacks.
Pretty easy. The cable should be 10ā² long total, and should compress to about 3ā².
Obviously I havenāt included all the details, but Iāll give the nitty-gritty when I make a full post on this.
Overall it will cost about $50, and Iāll be able to make two cables,
with each new one after that costing <$20.
All thatās needed is more PVC, Mylar, paracord, and jacks.
Much cheaper than the $70+ for a similar cord from Pexon.
I guess thatās about it. I already know what Iāll be working on when I finish all of everything,
but Iām going to keep that on the hush hush until I at least finish this Pokemon game.
Thatās it for now so until the next time I decide to stop being a lazy bastard, goodbye and goodnight.
[Could not load media at startup. The media server may be down.]
Finished my āGifBackgroundifierā! Set an animated .gif as your background on a Windows computer!
I think itās pretty rad, and if youād like to try it just ask.
If your GIF doesnāt work, try opening it in GIMP and exporting it to a new file with the highest quality.
This is just the first version so I havenāt made an error popup,
but you can tell if your GIF doesnāt work by if the loading bar moves or not.
Windows only, sorry.
Feel free to send me ideas, suggestions, or complaints!
Warning: This program uses a decent amount of processing power, about 12% on my laptop.
Iāve finally got grass working beautifully,
and I actually discovered exactly how grass works through a funky glitch in FR/LG.
Hereās how it works: Use cut in the north-most grass in Route 1,
take one step into Viridian city, then go back down to the grass.
When you step on the empty tiles,
the grass particle effects show and you still have a chance of wild encounters.
I figured out that rather than having three separate particles
for each āpieceā of the animation
(the stepped on floor, the fluttering grass/leaves, and the part that stays on above your character),
itās all in one particle, that looks like this:
[Could not load media at startup. The media server may be down.]
and changes the depth during the animation.
Thereās just one frame in which the second image is behind the character which led me to the discovery.
The glitch isnāt listed anywhere I could find easily,
which reminds me of another visual glitch in Vermilion City, which is less interesting,
but showcases a glaring flaw with the layer system used in the generation 3 games.
[Could not load media at startup. The media server may be down.]
And lastly, hereās a comparison between what my game looks like now, versus what the real game looks like.
Pretty close, right? Forgive the input lag on the left screen (The real game).
[Could not load media at startup. The media server may be down.]
[Could not load media at startup. The media server may be down.]
Look at how far weāve come! Itās not really that much but itās exciting to me.
Here you can see animations for running, biking, and cliff jumping in action!
Cliff jumping still needs the offset for the sprite, the shadow,
and the dust particles when you land, but the movement and animation frames are all in place!
This gif is a bit lower quality than the last one, even though itās still at 15FPS,
but it gets the point across. Anyway, thatās it for now.
Make sure to follow if youāre interested in this project.
If you want to know whatās being done every day follow my
daily updates page.
It isnāt shown in the gif, but you can no longer phase through the cliffs going upwards.
[Could not load media at startup. The media server may be down.]
Collision and animations are looking good right now.
Hereās a gif of where Iām at.
It took me all day to figure out what the hell I was doing wrong with interrupting walking into walls.
Anyway, I also overhauled the movement and controls Iād put in yesterday
because I realized exactly how the real games handled it.
You can see travelling through areas right in this, which is nice,
and you can also see that I havenāt programmed in collision for cliffs yet. :x
I had to keep the gif at 15FPS to make sure it was under 2MB while not looking being mangled by compression,
but the game does in fact run at 60FPS, just like a real Gameboy game.
Thatās pretty much it for now.
Make sure to check the Live Updates page on my blog for live updates of what Iāve done or am doing. Bye!
[Could not load media at startup. The media server may be down.]
It may not look like much but this is really exciting progress for me and this project.
This is a Pokemon recreation Iām making with Slick2D/Java that Iāve been working on for the past two weeks.
Thereās no collisions or animations quite yet but those should be done within the next two days.
This game is a recreation of FR/LG thatās expanded to
several other regions in a single story line including Hoenn, Johto, and Orre.
The main goal is to have a āpureā Pokemon game in which all Pokemon can be found
and there are no weird Dex restrictions
(For example, not being able to evolve a Crobat in FR/LG before the national dex,
or having game specific Pokemon like Vulpix or Bellsprout).
The biggest problem is leveling throughout regions.
I think the best solution is to have a level lock until you beat certain gyms -
similar to how traded Pokemon of a high level refuse to listen.
If you have any ideas send me an email and Iāll think about it.
Remember to check my live update page for more information on what exactly is being done!
Last month I started re-playing Pokemon Fire Red on the VBA-M emulator.
I went into the game with the goal of catching as many Pokemon as possible.
After about two weeks I managed to catch 124 of the 151 in the game.
That magic number also happened to be the maximum number of Pokemon you could get in either of the games.
I still wanted to catch all the Pokemon though.
Iād never really be happy with it until I had all of them.
So I moved my save to Leaf Green and caught all the LG exclusives.
This brought me to a total of 138 caught, 149 seen.
I would also only capture Female Pokemon
and that I have one of every Pokemon in my box in numeric order,
but thatās besides the point.
The only Pokemon I couldnāt get were the 4 trade-evolvers, the two other starters,
the other fossil Pokemon (Omanyte), and the legendary Mew.
Omastar and Mew were the only Pokemon I hadnāt seen.
And I wanted them.
What really sucks though is that trading on VBA-M is and has been broken forever on FR/LG.
I thought: āHey, I program, I should help out with this and figure out the glitch!ā so I did just that.
Kind of... I opened up the source code in Visual Studio and got it to compile right - Success!
After a little while scouring the source I realized that I really didnāt know C++ well at all.
I gave up on that and decided I would make my own Pokemon game.
That it should run on all systems, be able to use a controller, have online trades,
have all the regions, and most importantly that you could catch all the Pokemon.
Five days later and here I am starting this dev blog.
But Iāve never really finished a project this big before,
so I donāt want this to get popular until I finish.
I donāt want to disappoint a lot of people.
If you find this I beg you not to get too excited until Iāve made considerable progress.
I do want to finish this though,
so if anyone stumbles upon this and notices Iām not updating the blog or working on the game,
please spam my email or telephone or anything you can.