Depthwell banner

Depthwell

41 devlogs
173h 21m 1s

Depthwell is a fractal mining incremental game. How deep can you explore?
Made with TypeScript, Zig, and WebGPU.

This project uses AI

AI was used for initial data structure planning/consulting, due to the complex nature of this project. All assets, concepts, and art was made by me!

Demo Repository

Loading README...

e

Shipped this project!

Hours: 27.56
Cookies: šŸŖ 877
Multiplier: 26.53 cookies/hr

Fully finished the complex inventory and mining logic! It’s not anywhere finished as a game yet, BUT at least it has functioning inventory and mining this time, and is starting to feel playable!

e

Inventory logic is now fully functional! See the video (or click on the Demo button) to see how it works.

0
e

After some extensive bug-fixing, you can now directly select inventory slots, and the cursor also intelligently changes! The logic is now much more robust and the inventory actually feels pretty usable now. :)

Attachment
0
e

Did some research and I’ve fixed the color space that the game is using (with the shader)! The difference is effectively imperceptible, but there’s slightly higher contrast with sprites, and more importantly, it can fully use the rich color space of your device! [EDIT: Sadly, in the screenshot, it’s compressed by Flavortown and impossible to tell since it’s more washed out.]


If you’re wondering what I’m going on about, you can see for yourself if you have a device that supports the P3 color space: https://oklch.com/
There are significantly more rich colors you’re missing out with plain sRGB/RGB, which is one nice thing that the custom shader I built can do with little performance cost.

Attachment
0
e

Made the inventory look even better with wobbling animations, and made it so that if you break a block, the mushrooms/ceiling flowers/spiral plants are affected automatically and get placed! The animations and overall usability is much better.


I wasted about 3 of these 5 hours tracking down a dangling pointer bug, WebGPU buffer init bug, and Sprite bug. But at least they’re all fixed now, and the result is a fully working inventory! Also improved the Sprite struct to have compile-time boundary checking so I don’t make silly mistakes again.

Attachment
0
e

Fully fleshed out inventory design! The tilde/backquote key is now assigned to mining and it now only shows blocks you’ve mined with proper logic for placement too. This was easier than I expected, and the game is starting to become playable!
(The gradient effect is done through the ā€œLCHAā€ filter, and looks quite nice while being very cheap to implement!)

Attachment
0
e

Fairly small change this time: added some basic masks to the inventory (you can see the outlines, I might improve these later). See second image for what the masks look like! The ā€œinventoryā€ is now fully functional in terms of mining and using keyboards, but there’s still a few things to tackle like using the mouse to select items and only having a limited amount.

Attachment
Attachment
0
e

Worked on adding arbitrary entity rendering! This will allow me to create an inventory, work on particles, and block dropping. (Explanation in second attachment.) The inventory also has a second set of entities for shadows :)


Finally, I also got number rendering working by adding digits to my sprite sheet; you can see a full demonstration of everything thus far in the video attached at the end.

Attachment
Attachment
0
e

Started working on HP and mining! You can see the new sprite sheet (first image), an example of it in action (second image). Now, when you have the ā€œnoneā€ sprite selected (by default), you’ll mine blocks, and if you have another type, it will mine the block and swap it out for the new type selected for a frame. [Try this out in the Demo link for yourself!]

Next up, I’ll tackle adding a new section to my WGSL shader to let me draw arbitrary sprites at any location (and rotation). This will be used for item pickups as well as rendering an inventory!


I also spent about half of these 5 hours reorganizing my codebase to use folders: Zig syntax was giving me a bit of trouble there. This will help me code faster in the future as I can use shortcuts (like Cmd+P) more effectively instead of having to manage tabs!

Attachment
Attachment
Attachment
0
Nullskulls

Tagged your project as well cooked!

šŸ”„ Nullskulls marked your project as well cooked! As a prize for your nicely cooked project, look out for a bonus prize in the mail :)

e

Shipped this project!

Hours: 145.79
Cookies: šŸŖ 3166
Multiplier: 21.72 cookies/hr

I’m glad I got so much of the core architecture of my project down, and that it actually looks decent! Although I’m still working on and refining depth increase (because the specific algorithms are incredibly complicated) the foundation is in place for me to continue to improve this project and get a demo done on time.

I think the heavy emphasis on optimizations and caching will help a lot in the long run; even though there’s not a bunch of visual output for a 100+ hour project there’s a lot going on in the inside.

Hope ya like it!

e

Lots of misc changes with performance and caching! There was a bug that resulted in flawed cache logic; it’s now been fixed. Here’s an image showing an example of me messing with this editing!
(Important note! If you modify a chunk and then try to use the debug UI sliders, then the saved chunks will take priority, that’s intentional.)

Attachment
0
e

A lot of changes! You can see from the video that I’ve finally added some fancy sliders. These are in zig/debug_ui.zig and are passed to JS to create the HTML elements. It makes it way easier to test variable options now and it wasn’t as hard as I expected!
You can also see I got gravity and physics working, which is a big step of my project ready. :)


If you’re wondering why the sliders are so laggy it’s because some of them require regenerating 256 chunks. With normal movement and the gradual chunk loading logic there would only be 4 chunks being loaded a frame, which is much faster!


Also worked on making the ores look visually brighter (second image)! They turned out better than I expected.
Finally I tested out the shader to make sure it could function with sprites with multiple columns (third image), so that’s what I’m doing now. Even made some improvements to reduce ā€œtexture bleedingā€ artifacts even more!

Attachment
Attachment
0
e

Added more stone sprites (tiling is much less apparent), and worked on making gems shiny. Doesn’t look too good yet but it’s a start.

Attachment
0
e

Added some new sprites (you can see the copper, lava, and other strange stone purple sprite here). Also tuned procedural generation some more and applied SimBuffer logic (see second image for details) and did some behind-the-scenes stuff to allow multiple opacity values in 1 frame using GPU bind group/tile buffer swapping.

Specific benchmark details: I’m seeing performance rendering from initialization at ~250ms (well within acceptable time limits), and when moving in any direction (diagonal or cardinal), the ā€œworstā€ frames in terms of performance are ~5ms on my M1 Air, an improvement of 16x over a naive SimBuffer and around 4-8x (depending on zoom) with ChunkCache and no simulation buffer. Quite nice!


Glad to see progress is finally speeding up! I certainly feel like I’ve gotten a lot more done in the past 50 hours vs. the first 50. That’s the way it is once you set up the basics well I suppose :)

Attachment
Attachment
0
e

I iterated on procedural generation even more! Also got rid of LCH (light/chrome/hue) variation per-block as it looks kind of weird. While the art isn’t very good, the ore pockets and stone variations look a lot better now! I’m happy with the results.

Attachment
Attachment
0
e

Improved ore generation! The look is still pretty bad unfortunately but at least there are pockets of ores.
Screenshots show some examples as well as a heatmap. Still a lot of work to be done, but it’s acceptable for now.

Also, did more research on optimization! It turns out that there are a bunch of wasm-opt flags I missed previously: I’ve added them to build.zig now.

Attachment
Attachment
Attachment
0
e

Images show the improved procedural generation! The first one is a heatmap showing what my modified fractal brownian motion+Worley noise outputs. You can see how the cave-style generation is slowly taking shape: it’s not there yet in terms of being able to place structures, but at least it’s something.

I’ve also made the algorithm >20 times faster by using a simple hash implementation instead of ChaCha12 for generating blocks.


Images in order: heatmap, basic terrain (slightly older version), terrain+extra features/decor.

Attachment
Attachment
Attachment
0
e

I got a bit carried away for these two hours working on shader stuff! It should perform much better on lower-end devices now.

Attachment
Attachment
Attachment
0
e

Started working on more procedural generation logic! As you can see, here is a (very terrible) attempt at creating some background terrain. I have a plan with where this is going, which I’ll explain more in the next devlog!


Also spent 3 hours working on a way to layer 2 background and tile layers together with opacity. I’m kind of shocked it took that long, but it will come in handy in the future when I need to make a zoom animation and mix backgrounds together.

Attachment
0
e

Started working on fancy shaders for the background! Fractal brownian motion appears quite nice. Also made a few recolored iron ore sprites for testing. :)

Attachment
1

Comments

Jack
Jack 29 days ago

Wow, I really love the idea of this game, can’t wait to see it finished!

e

Worked on a bunch of stuff, mostly relating to getting depths beyond 16 fully working! You can see an example of it work in the first screenshot (the world spans over 100 digits of blocks technically :O), and the explanation for how it works in the second (it’s quite a lot). Quite happy this worked out! The reason why the quadrant paths are gigantic numbers are because it’s compacting a bunch of 4-bit nibbles of data (0-15) together: the suffix array is expanded to make it easier to see but that’s technically also just a 64-bit integer.


I also successfully figured out what I want to do for allocation! I’m using a big arena allocator that’s storing the suffix array, which is a SegmentedList to prevent memory fragmentation. Although this list is going to be very small, this will certainly help me in the future when I plan to store long-term modifications!

Attachment
Attachment
0
e

Short devlog time, because most of this time was spent making new sprites! The iron ore took by far the longest, but the result was quite worth it, I think. Was a bit shocked that the colors (similar-looking to Terraria’s) end up looking quite decent when you zoom out a bit (definitely still room to improve though).

Attachment
0
e

Some fairly small changes. Made edge flags work across chunks, eliminated unnecessary structs, and made other small changes! Finally ready to tackle seeding (I’m working on some planning documents).

As you can see from the image, the blocks across chunks are finally connected (you can tell from the lack of pixel erosion and darkening effect of the edges)! Thanks to the chunk cache, this is incredibly fast. (I ended up increasing the cache size from 64->128 to hold more data inside.)

Attachment
0
e

Spent like 4 hours trying to debug an issue with non-deterministic seeding! Turned out I was getting really weird undefined behavior with BLAKE3 hashing, which is what determines how the chunks are seeded.


Ended up fixing that; currently testing procedural generation for individual chunks! You can see it unfortunately has brief lag spikes; that’s with some basic cache logic too, so I’ll have to work on performance improvements some more and cook up some procedural generation algorithms.
I have some concepts already, now that I have the basics down the amount of stuff I can do has increased a lot :)

0
e

Fixed some misc issues, switched to ChaCha12 (which supports arbitrary skipping while offering similar performance, for procedural generation in the future), started working on modification/cache tier logic. Also fixed stack size crash due to not using & and improved logging!


As you can see from the screenshot, pixel erosion and edge flag logic has been added back in. Notice how these sprites appear to have a border! I’m still working on improving this; you can see it looks pretty chunky when there’s a lot of blocks nearby. I’d also like to implement a ā€œcolor mixingā€ feature where nearby pixels at the edge of a block blend, so connections between different-colored stone or ore blocks look much smoother. This will probably take a while though!

Attachment
0
e

Added the ability to test zooming in, made small WGPU improvements with color spaces and modern Firefox support, centered player, added more CPU features, improved WGPU seeding, and performed various fixes.


Screenshotted: an example of Firefox functioning! Note how the depth is 7 already, and the active suffixes are huge. The infinite nature of the game is finally beginning to take shape!


I plan to investigate procedural algorithms and work on migrating from Xorshiro512** to ChaCha8/12 next and work on pixel erosion logic on the side. ChaCha-family algorithms are pretty good CSPRNGs, meaning I can ask what would happen if I called .next() X times quickly, and it also might be faster with SIMD, which is even better! They also easily let me have 512 bits of seeding data. Really excited about this!

Attachment
0
e

Worked on a bunch of things! The README is now much more clear and outlines more details of the architecture goal I want to achieve. I also worked on inserting logging of the FPS with some tiny text in the top corners because I was debugging weird FPS issues.


It turns out that the ā€œaccumulatorā€ approach to frames works best; basically, the way it work is: instead of having a separate requestAnimationFrame and setTimeout, you ā€œaccumulateā€ new ticks, including fractional ones that carry over. Since I know that rendering is the main bottleneck for now, I don’t need to bother capping the logic!

I’m honestly not even sure how not implementing this accumulator logic caused a failure; this confused me for several hours because I thought WebGPU and requestAnimationFrame were unlikely to cause bottlenecks, and the inspector agrees. Yet, it did, so I had to use this approach. Kind of silly, really; I’ve implemented this type of logic before but thought it wasn’t needed this time and got proven wrong.


Anyway, I’ve been working on the architecture bit-by-bit, and have started working on storing modifications and handling depths above 16. This is going to take a while, but it should be worth it!

Attachment
Attachment
0
e

Started working on more of the architecture of the project! You can see a small portion of the changes to the README I made in the attachments (you can view this on the GitHub repo), as well as a quick performance test of my code: it appears that even on Debug mode in Zig, the main bottleneck is GPU calculations; even regenerating and all 256 chunks appears to be fine on my my machine. Quite nice!


Currently, progress is going much faster than it was, say, 30 hours of devlogging time ago. At this rate I might be able to get to…barebones physics…in 5 hours. (Turns out making a fractal chunk architecture is quite difficult indeed.)

Attachment
Attachment
Attachment
0
e

[Click to play the video, which shows a full demonstration of the changes!] Added even more documentation, fixed bugs involving player/camera/viewport, made camera position an integer and added better interpolation handling, improved logger handling for vectors, and simplified TypeScript logic! Also made the build script more robust. Wireframe rendering is also a bit prettier. :)


I also learned how to set up global keyboard shortcuts for VSCode (see second image), which is nice. Since Zig often requires you to do a lot of casting, these will probably save me a lot of time in the long run! Now, I plan to work on making sure that chunk generation is correct in the other corners of the map and making sure the fractal architecture actually works before moving on to making a ModificationStore (which stores whenever the player modifies a block).

Attachment
0
e

[Click on video to play!] Worked on smoother, cleaner camera movement that’s similar to Celeste, and added custom debug logging methods. You can now render text directly to any of the 4 corners of the screen!

===

Also changed build.zig logic to be more performant and only write to enum data if important files were actually changed. This has…improved build times by about ~600ms…and took me an hour. But at least it’s significantly faster!

0
e

Added a few new sprites, improved rendering, completely redesigned chunk system, fixed bugs and issues, made constant use more consistent, and also various misc changes. Changed a BUNCH of logic behind the scenes and fixed some bugs, I’ll add it to the README once it’s fully finished! Progress has started getting significantly faster :)

Attachment
0
e

Revamped chunk generation is coming along slowly, going to make more assets over time! Started working on a planning drawing with Excalidraw in Obsidian and looked into what others have made.
Turns out, there’s surprisingly little on the concept that I have, which is mainly water-focused and uses fairly repeating 16x16 textures. Only time will tell if that concept turns into reality successfully!

I’ll probably have to change the way I’m drawing with Aseprite [not included in Hackatime], currently I’m just messing with the filters and I should probably find a more robust way to do things, while using a color palette. Considering the mushrooms took me about 3 minutes it’s probably not as daunting as I thought to restrict to ~8-12 colors per sprite.

Attachment
Attachment
0
e

Started working on procedural generation! (Yes, it looks super weird right now, but I’ll need to actually start making sprites).

Attachment
0
e

Did some misc things! Improved rendering, switching to OKLAB and better constants for rendering, and removed zig build test in favor of robust native testing.

Attachment
0
e

Added some more block types to make testing nicer! Also added documentation on how to build the project.

Attachment
Attachment
0
e

I finally got rendering working (it was due to a sprite ID bug and operations that clamped to edge in WebGPU)! I’ve also added wireframes for debugging. Now, I can start implementing the chunk cache logic :D

Attachment
0
e

Camera panning is now smooth [see video] with friction! Also started designing the datatype and system that will and looked at how WebGPU works (not counted in time). Currently working on a new Chunk algorithm. Basically, I’m currently working on an implementation (hence the broken sprites) where chunks are 16x16. The most difficult part is passing this data to TypeScript as I’ve already finished the datatypes.
@@@@@
See the second attachment for my current updated plan. After I’m done with that part of the plan, I’ll probably start working on some sprites and ask others for advice.
It turns out that making your own game engine with low-level languages is quite hard 😭

Attachment
1

Comments

e
e about 2 months ago

(The specific bug here turned out to be that the sprites’ coordinates were going into the hundreds of thousands because I had initialized the sprite IDs wrong in Zig. It was very weird visually with all the y-axis stripes though!)

e

Made a bunch of changes, below:


Batch 1: Fixed native builds and build location, changed ColorRGBA to use vector internally
Batch 2: Worked on Vite build, changed JS/TS spacing to 4, added WGSL minifier
Batch 3: Added seeding options, fixed Vite more, fixed color_rgba, added more types to start sending keyboard data, actually fixed ColorRGBA
Batch 4: Added robust player and keyboard logic, in-place scratch buffer reallocation, connected tick() code between WASM and JS, better type generation


Basically, the way the keyboard logic (inputManager.ts) works is it maps multiple keys (such as W, Space, Up all to ā€œupā€) to one direction. It also has an advanced system for Simultaneous Opposing Cardinal Directions. If you press LEFT, then RIGHT, then lift RIGHT but LEFT is still held down the LEFT key remains. This code also does all of that with advanced bitmasking to get sent to Zig.


For the scratch buffer logic, I have a run_scratch_allocation_tests() function that tests scratch buffer expansion strategies in zig/memory.zig. The scratch buffer starts off at 256KiB but may in-place memory copy, saving precious WASM memory resources. I’ll never have to worry about data transfer again.


I also finally got camera panning working in the final version! (Not really a screenshot, so not shown in attachment). Attachments show:

  1. the log of keyboard bits (which are powers of two) working per-frame
  2. a screen-shot of the (dynamically generated!) enums.ts code on the left, which is starting to really pay off; it generates the memory addresses automatically between Zig and TypeScript! quite proud of this one
  3. my notes in Obsidian (great app for planning!)

Since the camera logic works and base logic is set in-stone, I’ve learned a LOT about Zig, how to make an actually robust program, and can hopefully enjoy the documentation I set for myself later on. (This is more fun than just using Godot/Unity, right??)

Attachment
Attachment
Attachment
0
e

Removed optimized float mode (as it’s not needed for this game), added proper buffer string reading/writing, changed seeding algorithm, improved enum auto-generation, and did a bunch of other changes!

From the first attachment, you can now see that there are plenty of new properties, including more exports! I should be able to implement the command system fairly easily by just giving some data for SCRATCH_INFO_VIEW through an enum.
The second attachment shows the dynamically generated enum file from Zig! Zig is awesome :)

Attachment
Attachment
0
e

Made comments more consistent, fixed Zig export location, made zoom property and added aspect ratio enforcement! As you can see from the image, the Canvas is now ā€œlogicallyā€ 480x270 but shaders still work with higher resolutions and factor in devicePixelRatio.
Should be able to start working on implementing a command buffer to communicate between TypeScript and Zig, camera, drawing test player, and having Zig generate some test tile data next.

Attachment
0
e

Worked on getting WGSL working and planned out my next steps for this project! (See second attachment)

Attachment
Attachment
0
e

Added more functions and started working on memory management! Really getting the hang of Zig now, gonna tackle getting to know WebGPU well next :)

Attachment
0
e

Finally finished getting Zig to properly log things with stack traces, and got the hang of what I’m doing :D (Yes, this took 5 hours.)
Trying an approach with this project where everything is clearly documented to minimize technical debt. It’s now time for me to implement pixel art rendering with BMP files!

Attachment
0