• Posted on

    Stuff I've been working on

    It’s been around 2 years that I’ve had to stop with my long-term addiction to stable jobs. Quite a few people who read this blog are wondering what the hell exactly I’ve been doing since then so I’m going to update all of you on the various projects I’ve been working on.

    Guess we're doing goblin games now
    Meme credit: Fabian Stadler

    Mikochi

    Last year, I created Mikochi, a minimalist remote file browser written in Go and Preact. It has slowly been getting more and more users, and it’s now sitting at more than 200 GitHub stars and more than 6000 Docker pulls.

    Mikochi star history

    I personally use it almost every day and it fits my use case perfectly. It is basically feature-complete so I don’t do too much development on it.

    I’ve actually been hoping users help me solve the few remaining GitHub issues. So far it happened twice, a good start I guess.

    Itako

    You may have seen a couple of posts on this blog regarding finance. It’s a subject I’ve been trying to learn more about for a while now. This led me to read some excellent books including Nassim Taleb’s Fooled by Randomness, Robert Shiller’s Irrational Exuberance, and Robert Carver’s Smart Portfolios.

    A portfolio overview in Itako

    Those books have pushed me toward a more systematic approach to investing, and I’ve built Itako to help me with that. I’ve not talked about it on this blog so far, but it’s a SaaS software that gives clear data visualizations of a stock portfolio performance, volatility, and diversification.

    It’s currently in beta and usable for free. I’m quite happy that there are actually people using it and that it seems to work without any major issues. However, I think making it easier to use and adding a couple more features would be necessary to make it into a commercially viable product.

    I try to work on it when I find the time, but for the next couple of months, I have to prioritize the next project.

    Dice’n Goblins

    I play RPGs too much and now I’m even working on making them. This project was actually not started by me but by Daphnée Portheault. In the past, we worked on a couple of game jams and produced Cosmic Delusion and Duat. Now we’re trying to make a real commercial game called Dice’n Goblins.

    The game is about a Goblin who tries to escape from a dungeon that seems to grow endlessly. It’s inspired by classic dungeon crawlers like Etrian Odyssey and Lands of Lore. The twist is that you have to use dice to fight monsters. Equipping items you find in the dungeon gives you new dice and using skills allows you to change the dice values during combat (and make combos).

    Exploring Dice'n Goblins dungeon

    We managed to obtain a decent amount of traction on this project and now it’s being published by Rogue Duck Interactive. The full game should come out in Q1 2025, for PC, Mac, and Linux. You can already play the demo (and wishlist the game) on Steam. If you’re really enthusiastic about it, don’t hesitate to join the Discord community.

    Technically it’s quite a big change for me to work on game dev since I can’t use that many of the reflexes I’ve built while working on infra subjects. But I’m getting more and more comfortable with using Godot and figuring out all the new game development related lingo. It’s also been an occasion to do a bit of work with non-code topics, like press relations.

    Japanese

    Something totally not relevant to tech. Since I’ve managed to reach a ‘goed genoeg’ level of Dutch, I’ve also started to learn more Japanese. I’ve almost reached the N4 level. (By almost I mean I’ve failed but it was close.)

    635 kanjis studied
    A screenshot from the Kanji Study Android App

    I’ve managed to learn all the hiraganas, katakanas, basic vocabulary, and grammar. So now all I’ve left to do is a huge amount of immersion and grind more kanjis. This is tougher than I thought it would be but I guess it’s fun that I can pretend to be studying while playing Dragon Quest XI in Japanese.

  • Posted on

    How to publish your Godot game on Mac

    Since 2019, Apple has required all MacOS software to be signed and notarized. This is meant to prevent naive users from installing malware while running software from unknown sources. Since this process is convoluted, it stops many indie game developers from releasing their Godot games on Mac. To solve this, this article will attempt to document each and every step of the signing and notarization process.

    A sealed letter
    Photo by Natasya Chen

    Step 0: Get a Mac

    While there tools exists to codesign/notarize Mac executables from other platforms, I think having access to a MacOS machine will remove quite a few headaches.

    A Mac VM, or even a cloud machine, might do the job. I have not personally tested those alternatives, so if you do, please tell me if it works well.

    Step 1: Get an Apple ID and the Developer App

    You can create an Apple ID through Apple’s website. While the process should be straightforward, it seems like Apple has trust issues when it comes to email from protonmail.com or custom domains. Do not hesitate to contact support in case you encounter issues creating or logging into your Apple ID. They are quite responsive.

    Once you have a working Apple ID, use it to log into the App Store on your Mac and install the Apple Developer application.

    The Apple Developer app in the play store

    Step 2: Enroll in the Apple Developer Program

    Next, open the Apple Developer app, log in, and ask to “Enroll” in the developer program.

    This will require you to scan your ID, fill in data about you and your company, and most likely confirm those data with a support agent by phone. The process costs ~99$ and should take between 24 and 48 hours.

    Step 3: Setup Xcode

    Xcode will be used to codesign and notarize your app through Godot. You should install the app through the App Store like you did for the Apple Developer application.

    The Xcode home screen

    Once the app is installed, we need to accept the license.

    First, launch Xcode and close it. Then open a terminal and run the following commands:

    sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
    sudo xcodebuild -license accept
    

    Step 4: Generate a certificate signing request

    To obtain a code signing certificate, we need to generate a certificate request.

    To do this, open Keychain, and from the top menu, select Keychain Access > Certificate Assistant > Request a Certificate From a Certificate Authority. Fill in your email address and choose Request is: Saved to disk. Click on Continue and save the .certSigningRequest file somewhere.

    The certificate request form in Keychain

    Step 5: Obtain a code signing certificate

    Now, head to the Apple Developer website. Log in and go to the certificate list. Click on the + button to create a new certificate. Check Developer ID Application when asked for the type of certificate and click on continue.

    The certificate form from developer.apple.com

    On the next screen, upload the certificate signing request we generated in step 4.

    You’ll be prompted to download your certificate. Do it and add it to Keychain by double-clicking on the file.

    You can check that your certificate was properly added by running the following command:

    security find-identity -v -p codesigning
    

    It should return (at least) one identity.

    Step 6: Get an App Store Connect API Key

    Back to the Apple Developer website, go to Users and Access, and open the Integrations tab. From this page, you should request access to the App Store Connect API. This access should normally be granted immediately.

    The API form from appstoreconnect.apple.com

    From this page, create a new key by clicking on the + icon. Give your key a name you will remember and give it the Developer access. Click on Generate and the key will be created.

    You will then be prompted to download your key. Do it and store the file safely, as you will only be able to download it once.

    Step 7: Configure Godot

    Open your Godot project and head to the project settings using the top menu (Project > Project Settings). From there search for VRAM Compression and check Import ETC2 ASTC.

    Then make sure you have installed up-to-date export templates by going through the Editor > Manage Export Templates menu and clicking on Download and Install.

    Godot's Export Templates menu

    To export your project, head to the Project > Export. Click on Add and select macOS to create new presets. In the presets form on the left, you’ll have to fill in a unique Bundle Identifier in the Application section, this can be com.yourcompany.yourgame.

    In the Codesign section, select Xcode codesign and fill in your Apple Team ID and Identity. Those can be found using the security find-identity -v -p codesigning command: the first (~40 characters) part of the output is your identity, and the last (~10 characters, between parentheses) is your Team ID.

    In the Notarization section, select Xcode notarytool and fill in your API UUID (found on the appstoreconnect page), API Key (the file you saved in Step 6), and API Key ID (also found on the appstoreconnect page).

    Click on Export Project… to start the export.

    Step 8: Checking the notarization status

    Godot will automatically send your exported file for notarization.

    You can check the notarization progress by running:

    xcrun notarytool history --key YOUR_AUTH_KEY_FILE.p8 --key-id YOUR_KEY_ID --issuer YOUR_ISSUER_ID
    

    According to Apple, the process should rarely take more than 15 minutes. Empirically, this is sometimes very false and the process can give you enough time to grab a coffee, bake a cake, and water your plants.

    Once the notarization appears completed (with the status Valid or Invalid), you can run this command to check the result (using the job ID found in the previous command output):

    xcrun notarytool log --key YOUR_AUTH_KEY_FILE.p8 --key-id YOUR_KEY_ID --issuer YOUR_ISSUER_ID YOUR_JOB_ID
    

    Step 9: Stapling your executable

    To make sure that your executable can work offline, you are supposed to ‘staple’ the notarization to it. This is done by running the following command:

    xcrun stapler staple MY_SOFTWARE.dmg
    

    Extra: Exporting the game as .app

    Godot can export your game as .dmg, .zip, or .app. For most users, it is more convenient to receive the game as .app, as those can be directly executed. However, the notarization process doesn’t support uploading .app files to Apple’s server.

    I think the proper way to obtain a notarized .app file is to:

    1. Export the project .dmg from Godot with code signing and notarization
    2. Mount the .dmg and extract the .app located inside of it
    3. Staple the .app bundle

    Extra: Code signing GodotSteam

    GodotSteam is a Godot add-on that wraps the Steam API inside GDscript. If you use it, you might encounter issues during notarization, because it adds a bunch of .dylib and .framework files.

    What I did to work around that was to codesign the framework folders:

    codesign --deep --force --verify --verbose --sign "Developer ID Application: My Company" libgodotsteam.macos.template_release.framework
    codesign --deep --force --verify --verbose --sign "Developer ID Application: My Company" libgodotsteam.macos.template_debug.framework
    

    I also checked the options Allow DyId environment variable and Disable Library Validation in the export settings (section Codesign > Entitlements).

    FAQ: Is this really necessary if I’m just going to publish my game on Steam?

    Actually, I’m not 100% sure, but I think it is only “recommended” and Steam can bypass the notarization. Steamworks does contain a checkbox asking if App Bundles Are Notarized, so I assume it might do something.

  • Posted on

    Create a presskit in 10 minutes with Milou

    Talking to the press is an inevitable part of marketing a game or software. To make the journalist’s job easier, it’s a good idea to put together a press kit. The press kit should contain all the information someone could want to write an article about your product, as well as downloadable, high-resolution assets.

    The press kit of a Dice'n Goblins
    The press kit for our upcoming game Dice'n Goblins

    Introducing Milou

    Milou is a NodeJS software that generates press kits in the form of static websites. It aims at creating beautiful, fast, and responsive press kits, using only YAML configuration files.

    I built it on top of presskit.html, which solved the same problem but isn’t actively maintained at the moment. Milou improves on its foundation by using a more modern CSS, YAML instead of XML, and up-to-date Javascript code.

    Installation

    First, you will need to have NodeJS installed:

    curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
    nvm install 22
    

    Once Node is ready, you can use NPM to install Milou:

    npm install -g milou
    

    Running milou -V should display its version (currently 1.1.1).

    Let’s build a press kit

    Let’s create a new project:

    mkdir mypresskit
    cd mypresskit
    milou new
    

    The root directory of your project will be used for your company. In this directory, the file data.yml should contain data about your company, such as your company name, location, website, etc… You can find an example of a fully completed company data.yml file on GitHub. To validate that your file is a valid YAML file, you can use an online validator.

    Your company directory should contain a sub-folder called images, you should put illustrations you want to appear in your press kit inside it. Any file named header.*** will be used as the page header, favicon.ico will be used as the page favicon, and files prefixed by the word logo will appear in a dedicated logo section of the page (eg. logo01.png or logo.jpg). Other images you put in this folder will be included in your page, in the Images section.

    data.yml alongside images/
    The directory structure

    After completing your company page, we can create a product page. This will be done in a subfolder:

    mkdir myproduct
    cd myproduct
    milou new -t product
    

    Just like for a company, you should fill in the data.yml file with info about your product, like its title, features, and prices. You can find an example of a product file on GitHub. The product folder should also contain an images subfolder. It works the same way as for the company.

    The press kit for Pizza Burger studio
    The example press kit

    When your product is ready, go back to the company folder and build the press kit:

    cd ../
    milou build .
    

    This will generate the HTML and CSS files for your online presskit in the directory build. You can then use any web server to access them. For example, this will make them accessible from http://localhost:3000/

    cd build
    npx serve
    

    To put your press kit online, you can upload this folder to any static site host, like CloudFlare Pages, Netlify, or your own server running Nginx.

    Conclusion

    Milou is still quite new, and if you encounter issues while using it, don’t hesitate to open an issue. And if it works perfectly for you, leave a star on GitHub.

  • Posted on

    How to solve it (with raycasting)

    In 1945, mathematician George Pólya released the book “How to solve it”. It aims at helping math teachers guide their students into solving abstract problems by asking the right questions. It has since had a large influence on math education and computer science, to the point of being mentioned by Marvin Minksy as a book that everyone should know.

    In this post, I will try to see how we can use Pólya’s methodology to solve a concrete software engineering problem: rendering 3D objects (using Golang).

    A blue cubic sponge

    Understanding the problem

    Before starting to solve the problem, we must make sure that we completely understand the problem. The first question to ask ourselves is What is the data?

    The data is a 3D object. The object is made out of triangles. Each triangle is made out of 3 vertices (and a normal vector). Those objects are stored in .STL files. I will parse them with the hschendel/stl lib.

    The second question, which is probably the most important is What is the unknown?. Or in programming terms, What should the program output?

    Our program should output an image. An image is a 2D matrix of pixels, each pixel representing a color. The most common way of representing color is the RGBA model, which stands for Red, Green, Blue, and Alpha. In Golang, images can be represented using the image.Image data structure from the standard library.

    The third question is What is the condition (linking the data to the output)?

    The data gives us information about the space occupied by our 3D object. If the 3D object is in front of our pixel, this pixel should be in a different color. We will use the method known as “raycasting” which consists of sending a ray from each pixel, and checking what the ray hits.

    Devise a plan

    Now that we have understood our problem a little bit better, we should try to plan what our solution will look like. The most helpful question to come up with a solution is Do you know a related problem?

    Raycasting constists of sending a “ray” for each pixel of our image. If this ray intersects with our 3D object, the pixel needs to be updated to a different color. Since our 3D object is made entirely out of triangle, a related problem would be Does a vector intersect with a triangle?

    To solve this we can implement the Möller–Trumbore intersection algorithm. This algorithm transforms the above problem into two new questions Does the ray intersect with the triangle’s plane? and if yes, Does the ray-plane intersection lie outside the triangle?

    This first question is simple to solve, the only way a vector doesn’t intersect with a plane is if the vector and plane are parallel. In that case, the dot product of the ray and the triangle’s normal vector would be zero, since the dot product of two perpendicular vectors is 0 and the normal vector is itself perpendicular to the triangle’s plane.

    If the ray intersects with our triangle’s plane, then we can check if the intersection is inside the plane by switching to barycentric coordinates. Barycentric coordinates are a way to represent a point in a plane in relation to the vertices of the triangle. Each corner of the triangle will get the coordinates (0,0,1), (0,1,0) and (1,0,0). Any point outside of the triangle will get coordinates outside of the range [0,1].

    A triangle with a point P at the coordinates (a1, a2, a3)
    Visualizing barycentric coordinates

    Now that we know an algorithm that can solve our main issue, we can come up with the outline of our program:

    func MTintersect(ray, triangle) bool {
    	if isParallel(ray, triangle) {
    		return false
    	}
    	u , v := projectBaryocentric(vec3, triangle)
    	return u > 0 && u < 1 && v > 0 && u + v < 1
    }
    
    func main () {
    	solid := readSTL()
    	image := newImage(width, height)
    
    	for i := range width {
    		for j := range height {
    			image.Set(i, j, white)
    			ray := castRay(i, j)
    			for triangle := range solid.Triangles {
    				ok := MTintersect(ray, triangle)
    				if ok {
    					image.set(i, j, blue)
    				}
    			}
    		}
    	}
    
    	writePNG(image)
    }
    

    Carrying out the plan

    This is the easy part. We just write the code.

    The main suggestion that Pólya makes, is to check that every step of the solution is correct. While programming, this can be achieved by writing unit tests to ensure the correctness of our code.

    Looking back

    Once we have something that seems to work it is tempting to just git push and call it a day. But there are a few more questions we should ask ourselves.

    First Can we check the result?

    A good way to answer that is to test our program ourselves, either by manually going through a checklist or by writing an integration test that covers our problem.

    A book, a frog, a boat and the eiffel tower
    Results of rendering a few .stl files

    Then we should ask ourselves Can we derive the result differently?

    This question is not only a good way to learn about other ways to solve our problem (like Scanline rendering in our case) but also a good opportunity to check if maybe the code we wrote was not the most intuitive solution and could be refactored.

    The last question is Can you use the result for another problem?

    We can answer this question by checking if our code is written in a way that is reusable enough if we ever want to. For example, the raycaster above could be used as the first step into the implementation of a more sophisticated ray tracing algorithm, if we wanted to handle reflections and lightning.

    Conclusion

    If you want to check the source code for the raycaster I made before writing this article, it is on my GitHub.

    You can find How to solve it by Pólya in any good library.

    To learn more about computer graphics check out Ray Tracing in a weekend. And for the details of the Möller-Trumbore algorithm, this video is the one that made the most sense to me.

  • Posted on

    A love letter to Apache Echarts

    In the world of software development, I believe the 7th circle of hell is called “NPM”. It’s a place where you can find Javascript libraries that make great promises only to disappoint you once you actually import them. I know this because I even wrote one.

    When you find a gem hidden in this pile of garbage, it is important to tell the world about it. So, I am here to tell you how much I love Apache Echarts.

    A flow visualization
    A flow visualization from the Echarts examples

    What is this Echarts thing about?

    Apache Echarts is a data visualization library made by researchers from Zhejiang University and Baidu. It provides developers with a declarative language that helps you render charts to canvas or SVG elements.

    What makes it stand out from other charting libraries is that it manages to cover a very wide range of use cases while remaining simple to use. On top of that, the documentation never failed to point me in the right direction. And most importantly, the results look good and are interactive.

    A few examples

    Bar chart

    To get started, let’s do a simple bar chart of the stars in my GitHub repositories in 20 lines of code.

    Source code
    const bar = echarts.init(document.getElementById('bar'));
    bar.setOption({
      legend: {},
      tooltip: {},
      dataset: {
        // dataset.source contains your data in table form
        source: [
          ['repo', 'stars'],
          ['Mikochi', 129],
          ['crud_benchmark', 40],
          ['sql-repository', 7],
        ],
      },
      xAxis: {
        // category will use the first column for the x axis
        type: 'category',
      },
      yAxis: {},
      series: [
        // this creates a bar chart
        { type: 'bar' },
      ],
    });
    


    Financial chart

    If like me, you enjoy playing with financial data, you might be used to more complex charts. Echarts lets you easily combine multiple visualizations in the same canvas. The following chart uses candlesticks to visualize price fluctuations, a line for the averages, and bars for the volume.

    Source code
    const candles = echarts.init(document.getElementById('candles'));
    candles.setOption({
      title: { text: 'Apple Inc. Week 6 2024' },
      tooltip: {},
      dataset: {
        source: [
          ['date', 'close', 'open', 'lowest', 'highest', 'volume', 'sma50'],
          ['2024-02-09', 188.85, 188.65, 188.00, 189.99, 45155216.0, 190.48],
          ['2024-02-08', 188.32, 189.38, 187.35, 189.54, 40962047.0, 190.51],
          ['2024-02-07', 189.41, 190.64, 188.61, 191.05, 53438961.0, 190.54],
          ['2024-02-06', 189.30, 186.86, 186.77, 189.31, 43490762.0, 190.55],
          ['2024-02-05', 187.68, 188.15, 185.84, 189.25, 69668812.0, 190.59],
        ],
      },
      xAxis: {
        type: 'time', // automatically parses the dates
      },
      yAxis: [
        // scaled axis for the price
        { name: 'Price', scale: true },
        // hidden axis for the volume
        {
          max: 150000000,
          scale: true,
          axisLabel: { show: false },
          axisLine: { show: false },
          splitLine: { show: false },
        },
      ],
      series: [
        // this creates a candlestick chart using cols [0-5]
        {
          type: 'candlestick',
          yAxisIndex: 0,
          tooltip: {
    	formatter: (param) => `
    	  Date: ${param.value[0]}<br />
    	  Open: ${param.value[2]}<br />
    	  High: ${param.value[4]}<br />
    	  Low: ${param.value[3]}<br />
    	  Close: ${param.value[1]}<br />
    	`,
          },
        },
        // the volume gets mapped to a bar chart
        {
          type: 'bar',
          encode: { x: 'date', y: 'volume' },
          yAxisIndex: 1,
          tooltip: {
    	formatter: (param) => `Volume: ${param.value[5]}`,
          },
        },
        // SMA line
        {
          type: 'line',
          encode: { x: 'date', y: 'sma50' },
          yAxisIndex: 0,
          tooltip: {
    	formatter: (param) => `SMA50: ${param.value[6]}`,
          },
        },
      ],
    });
    


    Animated line chart

    One of the cool things about Echarts is that you’re not limited to creating static pictures. Charts are animated, which for example lets you create line races like this:

    Source code
    const makeOptionsWithRandomData = () => {
      // randomized dataset with 4 columns
      const dataset = [['x', 'y', 'y2', 'y3']];
      for (let i = 1; i < 25; i++) {
        dataset.push([i, i * Math.random(), i * Math.random(), i * Math.random()]);
      }
    
      return {
        // this will make the animation last 10000ms
        animationDuration: 10000,
        dataset: { source: dataset },
        xAxis: { type: 'category' },
        yAxis: {},
        series: [
          { type: 'line' },
          { type: 'line' },
          { type: 'line' },
        ],
      };
    }
    
    const race = echarts.init(document.getElementById('race'));
    race.setOption(makeOptionsWithRandomData());
    
    setInterval(() => {
      // reset the chart with new random data
      race.clear();
      race.setOption(makeOptionsWithRandomData(), true);
    }, 10000);
    


    Interactive pie chart

    The charts can also be made interactive using Javascript event listeners. Click on the chart below to change it from a Pie to a Treemap.

    Source code
    // steam players by editor and games
    const data = [
      {
        value: 1568930,
        name: 'Valve',
        path: 'Valve',
        children: [
          { value: 954936, name: 'Counter-Strike 2', path: 'Valve/Counter-Strike 2' },
          { value: 613994, name: 'Dota 2', path: 'Valve/Dota 2' },
        ],
      },
      {
        value: 434978,
        name: 'Pocketpair',
        path: 'Pocketpair',
        children: [
          { value: 434978, name: 'Palworld', path: 'Pocketpair/Palworld' },
        ],
      },
      {
        value: 286851,
        name: 'KRAFTON, Inc.',
        path: 'KRAFTON, Inc.',
        children: [
          { value: 286851, name: 'PUBG: BATTLEGROUNDS', path: 'KRAFTON, Inc./PUBG: BATTLEGROUNDS' },
        ],
      },
      {
        value: 147735,
        name: 'Electronic Arts',
        path: 'Electronic Arts',
        children: [
          { value: 147735, name: 'Apex Legends™', path: 'Electronic Arts/Apex Legends™' },
        ],
      }
    ];
    
    const pieOptions = {
      tooltip: {},
      series: [
        {
          type: 'pie',
          universalTransition: true, // smooth transition between chart types
          data, // treelike data can't be passed to dataset.source
        },
      ]
    };
    const treemapOptions = {
      tooltip: {},
      series: [
        {
          type: 'treemap',
          universalTransition: true,
          data,
        },
      ]
    };
    
    let isPie = true;
    const map = echarts.init(document.getElementById('map'));
    map.setOption(pieOptions);
    
    map.on('click', () => {
      // switch the options
      map.setOption(isPie ? treemapOptions : pieOptions, true);
      isPie = !isPie;
    });
    


    Try it yourself

    If you want to try it yourself, I suggest heading to the official tutorial. There you will find instructions about how to set up and start using Echarts, as well as how to customize it to fit into your existing design systems.

    As there are many features I didn’t showcase in this article (like 3D graphs), I also recommend taking a look at the official examples.

subscribe via RSS