“deployed and scaled containerized applications with kubernetes (amazon eks), improving resiliency and enabling 99%+ service uptime.”
résumé · may 2026
first role / interface
“enhanced a react/node onboarding interface with accessibility improvements, improving usability scores and broadening adoption for new users.”
résumé · may 2026jun–aug 2026 · weppi + aiproxy
model routes, written down
fallback codes · reasoning · verbosity · accounts
jun–aug 2026 · weppi + aiproxy
connect hermes to begin
fresh clone · no sessions loaded
feb 2026 · maps + transit
the delay table
markdown in · delay table out
feb 2026 · maps + transit
openbayan
open-source philippine public-service projects
opencode
“implement real-time mp4 video capture of the map animation.”
feb 2026 · transit visualizer
⋈
“bro what happened to claude code”
— johnislarry⋈
“ah sarado”
— lxsmnsyc
fixing tomodachi life's black grass in eden
a steam deck texture bug turned into a renderer debugging trail through descriptor stride, descriptor capacity, rescaling metadata, and spir-v nonuniform indexing.
wfd · 2026-05-03T00:00:00 · 6 min
replacing tailscale with headscale
tailscale's free tier is generous but i'd rather own the control plane. headscale is the self-hosted replacement and it runs fine on proxmox.
wfd · 2026-02-28T02:01:00 · 5 min
git.eden-emu.dev
3897: the 1024 descriptor discovery that this superseded
from fixing tomodachi life's black grass in edengit.eden-emu.dev
3899: rescaling metadata indexed by descriptor
from fixing tomodachi life's black grass in edengit.eden-emu.dev
3900: sampled descriptor indices as nonuniform
from fixing tomodachi life's black grass in edengit.eden-emu.dev
3898: dynamic texture descriptor strides
from fixing tomodachi life's black grass in edengit.eden-emu.dev
3905: integration branch
from fixing tomodachi life's black grass in edendocs.vulkan.org
vulkan descriptor sets
from fixing tomodachi life's black grass in edendocs.vulkan.org
vulkan descriptor indexing guide
from fixing tomodachi life's black grass in edendocs.vulkan.org
VkPhysicalDeviceLimits
from fixing tomodachi life's black grass in edendocs.vulkan.org
VkDescriptorSetLayoutBindingFlagsCreateInfo
from fixing tomodachi life's black grass in edenblog.mws.rocks
Optically Correct Liquid Glass
from exploring liquid glass for the webshadertoy.com
mws's shadertoy implementation
from exploring liquid glass for the webdeveloper.apple.com
Adopting Liquid Glass
from exploring liquid glass for the webgithub.com
liquidglass
from exploring liquid glass for the webgithub.com
Headscale GitHub
from replacing tailscale with headscaletailscale.com
CGNAT range
from replacing tailscale with headscaletailscale.com
subnet routing
from replacing tailscale with headscaleregistry.terraform.io
awlsring/headscale Terraform provider
from replacing tailscale with headscaletailscale.com
preauth key
from replacing tailscale with headscalegithub.com
GitHub issue 232
from oomkilling myself because of kubernetes event exporterdocs.fluentbit.io
chunkbased processing and filesystem buffering
from oomkilling myself because of kubernetes event exportergithub.com
original Opsgenie repository
from oomkilling myself because of kubernetes event exportergithub.com
resmoio/kuberneteseventexporter
from oomkilling myself because of kubernetes event exportergithub.com
GitHub issue 237
from oomkilling myself because of kubernetes event exporter
unfolding paper…
wfd / May 3, 2026 / 6 min
fixing tomodachi life's black grass in eden
what this is
this is a write-up of the tomodachi life texture debugging run in eden. it started with one visible bug: on steam deck, Tomodachi Life: Living the Dream rendered the town ground as black voids while the sand, buildings, water, and most props looked fine. it ended with three separate pull requests, one integration branch, several crash investigations, and a clearer understanding of a piece of the renderer that eden inherited from the yuzu-era codebase.
the short version:
black ground / void terrain -> dynamic texture descriptor stride was wrong
corner corruption -> dynamic descriptor arrays needed room past 64
grass/path speckles -> sampled texture descriptor indices needed NonUniform
nvidia device loss -> still isolated to the nonuniform PR, not solved yet
black water at angles -> separate issue, not solved by this work
the important part is that these looked like one bug from the outside. they were not. the game happened to stack several renderer edge cases in the same town scene.
why i cared
i only wanted tomodachi life to stop looking cursed on my steam deck.
the first screenshots made it look like a texture decode failure. the ground was black, some grass and paths were missing, and a tonemapped view made the broken areas stand out as if the terrain had fallen into a void. but the bug was selective. buildings were fine. sand was fine. water was fine. signs, rooftops, props, and UI were mostly fine. that shape mattered because a global vulkan failure, png decode issue, astc issue, or general texture-cache failure would have damaged far more than one family of ground materials.
so the first real clue was not what was broken. it was what was not broken.
the setup
the reproduction target was a steam deck running eden through an AppImage. most of the loop looked like this:
build eden locally or on the deck
copy the AppImage into /home/deck/Applications/Eden.AppImage
launch the game on deck
run probes, flags, or hardcoded shader changes
dump logs, shaders, and texture payloads
pull the dump back and inspect it
repeat
we also had to fix the tooling as we went. the dump script started as a quick archive helper and slowly turned into a real debugging tool with progress output, copy progress, cleanup, shader indexing, descriptor diagnostics, and enough guardrails to avoid filling the deck with old tarballs.
that part was not glamorous, but it mattered. when the feedback loop is "build, copy, boot, run, dump, inspect", bad tooling turns every hypothesis into a slog.
the first false trails
the first wave of guesses was broad:
maybe the shader translator emitted bad code
maybe the texture contents were corrupt
maybe the descriptor table was picking the wrong handle
maybe the depth pass was rejecting the wrong pixels
maybe rescaling metadata was out of sync
maybe amd/radv had a driver-specific path that needed different spir-v
we tested those by forcing colors, dumping shader payloads, overriding descriptors, splitting shader outputs into channels, and changing depth compare modes. a lot of those probes were intentionally ugly. the point was not to ship them. the point was to make one part of the pipeline scream.
some probes were especially useful:
probe 20:
changed the top ground layer to magenta.
this told us the broken terrain was reachable through a sampled descriptor path.
e0 force-white:
showed the correct per-tile material layout, but in a single forced color.
this told us the topology and tile selection were not dead.
depthalways / nodepth:
removed some speckles but made edges look wrong.
this told us depth was involved in later artifacts, but not the black texture root cause.
sampled nonuniform:
fixed grass/path speckles on steam deck.
this split the speckle bug away from the black texture bug.
the biggest early mistake would have been forcing all of these into one theory. the probes only started making sense once we let the symptoms split.
root cause 1: descriptor stride
the black ground was a descriptor selection bug.
eden's texture pass had long assumed that dynamic texture descriptors were indexed as 8-byte entries. that is the normal descriptor handle size:
the old rewrite effectively treated the dynamic byte offset like this:
index = dynamic_offset >> 3;
that is only correct if the descriptor table is tightly packed as 8-byte handles. the failing terrain path was not. the guest shader's dynamic offset was shaped like a left shift:
dynamic_offset = index << shift
that means the descriptor stride is 1 << shift, not always 8. if the recompiler divides by 8 while the guest shader is spacing entries by a larger stride, the shader samples the wrong handle. in this scene, the wrong handle produced black terrain.
the fix was to infer the stride from the IR and carry it through the descriptor metadata.
IR::Opcode::ShiftLeftLogical32 was the opcode that made the bug concrete. it told us the shader was not asking for "descriptor n in an 8-byte table". it was asking for "descriptor n at this shifted byte offset".
why the count also had to change
once stride was correct, count became the next problem. the old .count = 8 was not enough, and the first cleaned-up attempt with 64 still left corner corruption. lizzie's #3897, based on Mythrax's work, showed that a practical 1024 cap fixed related texture issues in other games. that PR used a blunt raw-slot model, but the discovery was valuable: some shaders really do need access to descriptor arrays much larger than 64.
the version we landed into the stride fix kept the cleaner stride-aware model, but raised the practical ceiling and bounded it by the constant-buffer window:
the 1U + (available - DESCRIPTOR_SIZE) / stride part matters. it counts how many 8-byte descriptors can fit at the requested stride. if there are 24 bytes left and the stride is 16, two descriptors fit: one at offset 0 and one at offset 16.
we removed the earlier heuristic idea that scanned cbuf contents for null handles or repeated values. that version worked for the scene, but it guessed from data. the final version is deterministic: infer stride, calculate room left in the cbuf, clamp to the maximum.
why 1024 did not mean "ignore limits"
raising a descriptor array to 1024 is not free. vulkan counts descriptor array elements against descriptor limits. a binding with descriptorCount = 1024 costs 1024 descriptors, not one. the safe version needed to respect host device limits instead of blindly exposing 1024 everywhere.
the sampled texture cap now budgets against the relevant vulkan limits:
this was a good example of why "the switch only has 32 descriptors" was the wrong framing. the provided switch gpu-info dump had maxBoundDescriptorSets = 32 and maxPushDescriptors = 32, but sampled-image descriptor limits were much larger. those are different limits. descriptor sets, push descriptors, and descriptor array elements are not interchangeable terms.
descriptor count > maxPushDescriptors -> push descriptors disabled -> normal descriptor set layout
that does not make the change risk-free. switching a layout away from push descriptors can still expose path differences, and some 2d/ui rendering paths care. but the "32" limit was not a reason to cap sampled texture arrays at 32.
root cause 2: rescaling metadata
the rescaling fix became its own PR because it was adjacent, not the same bug.
when descriptor arrays grew, rescaling metadata had to stay aligned with how descriptors were indexed. otherwise, the shader could select one descriptor while the metadata cursor described another one. this did not fix the black ground by itself, and it did not solve the ocean issue, but it was a correctness fix for descriptor-indexed sampling.
that became #3899, separate from the stride work. keeping it split was the right call because it let us test:
#3898 alone -> no crash in isolation
#3899 alone -> no crash in isolation
#3900 alone -> confirmed crash path on nvidia
that isolation would have been much harder if all three changes lived in one giant PR.
root cause 3: NonUniform
after the black terrain and corner corruption were fixed, the grass/path area still had speckles on steam deck. the descriptor data was no longer obviously wrong. the remaining artifact looked like per-lane descriptor selection going bad.
that pointed at spir-v NonUniform.
in vulkan, if a descriptor array index can vary across lanes, the compiler needs to know that. otherwise, a driver can optimize as if the index is dynamically uniform. on amd/radv, the bad assumption showed up as wrong sampled texture selection in the grass/path materials.
the fix was to decorate the sampled texture descriptor index path:
and then apply it to the sampled texture access chain:
const DescriptorIndex idx{ctx, index};
const Id pointer{ctx.OpAccessChain(def.pointer_type, def.id, idx.Value())};
idx.Decorate(ctx, pointer);
const Id object{ctx.OpLoad(def.sampled_type, pointer)};
idx.Decorate(ctx, object);
return object;
we scoped this down after testing. the first version touched sampled textures, texture buffers, storage images, and storage texel buffers. that was too broad. the useful proof was on sampled texture arrays, so #3900 moved toward a sampled-texture-only shape.
that fixed the steam deck grass/path speckles. it also opened the current unresolved thread: nvidia device loss still starts at #3900, even after the scope reduction. that means the idea is probably right for amd/radv, but the implementation or driver interaction is not merge-safe yet.
the debugging timeline
1. make it reproducible
the first job was boring: get an AppImage that actually launched on the deck, avoid the vulkan loader mismatch, and make sure the binary under test was the one steam was launching. this mattered because at one point desktop mode and steam launch were not running the same binary, which made results look contradictory.
2. dump everything
we built a dump script that captured eden logs, shader dumps, pipeline cache data, texture probe output, and relevant runtime files. the script broke several times under real use, including unexpected EOF bugs and slow copy/index phases, so it gained progress indicators and cleanup.
3. force the renderer to confess
we ran probes that forced colors, swapped descriptor outputs, split channels, and changed depth compare behavior. most probes did not fix anything. that was still useful because they narrowed which subsystem could be wrong.
the important observation was that forcing certain descriptor paths changed exactly the terrain materials we cared about. that pushed the investigation away from png/astc/decode and toward descriptor indexing.
4. find the stride mismatch
the descriptor probes led to the dynamic offset expression. ShiftLeftLogical32 was the moment the original hardcoded >> 3 model stopped making sense. once the code carried size_shift through ConstBufferAddr and descriptor metadata, the black ground stopped being black.
5. compare against the brute-force fix
#3897 looked hackier but fixed some cases we still missed. instead of copying it literally, we separated the useful discovery from the implementation: keep the 1024 capacity, but preserve stride correctness and clamp by cbuf and device limits.
6. split the fixes
the work became three PRs:
#3898: dynamic texture descriptor stride and capacity
#3900: sampled texture descriptor indices marked nonuniform
then #3905 became the integration branch for testing them together.
7. isolate the cross-driver fallout
steam deck was not the whole world. once the deck path looked good, intel and nvidia reports exposed separate risks. the important isolation result so far:
#3898 -> no crash in isolation
#3899 -> no crash in isolation
#3900 -> confirmed nvidia crash path
that changed the current hypothesis. descriptor capacity and rescaling are not the direct nvidia crash trigger. the sampled nonuniform path is.
what we solved
we solved the original steam deck symptom. the black/void ground came from wrong dynamic descriptor stride handling. the final stride-aware descriptor path fixes that without scanning descriptor contents or guessing from repeated handles.
we also fixed the corner corruption that remained after the stride fix by allowing dynamic descriptor arrays to grow to the practical 1024 cap, while still bounding by the cbuf window and vulkan descriptor limits.
on amd/radv, the grass/path speckles were fixed by marking dynamic sampled texture descriptor indices as NonUniform.
what is still open
the nvidia device-loss issue is still open. current evidence says it starts at #3900, not #3898 or #3899. it also predates the OpImage decoration cleanup, so the crash is not simply "we forgot to decorate the image result".
the next probes are smaller:
force sampled nonuniform support off on nvidia
try Texture() index-only decoration
try Texture() pointer-only decoration
try Texture() object-only decoration
try index + pointer without object
compare against the current sampled-all version
if every sampled variant still loses the device on nvidia, then the mergeable path may need a driver gate, a shader allowlist, or a more precise condition for when the descriptor index is actually nonuniform.
the black water at certain camera angles is also still open. it did not respond to the descriptor stride fix, the 1024 cap, rescaling metadata, or sampled nonuniform indexing. it should be treated as a separate renderer bug.
what i learned
the biggest lesson was not "descriptors are hard", even though they are. the real lesson was that visual bugs lie by grouping unrelated failures into one screenshot.
black ground, speckled grass, broken corners, and black water all lived in the same scene. only one of them was the original descriptor stride bug. one was capacity. one was spir-v nonuniform indexing. one is still something else.
the second lesson was to keep fixes split. #3898, #3899, and #3900 each changed a different layer. because they were separate, we could later say with confidence that the nvidia crash was #3900, not the descriptor capacity work. if this had shipped as one mega-patch, that isolation would have been painful.
the third lesson was terminology. "32 descriptors" meant nothing until we said which limit we meant. push descriptor count, bound descriptor set count, per-stage sampled image count, descriptor set sampled image count, and descriptor array length are different things. mixing them up leads to bad fixes.