<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Charles Zhang</title>
    <description>The latest articles on DEV Community by Charles Zhang (@methodoxdivooka).</description>
    <link>https://dev.to/methodoxdivooka</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3131834%2F92cc88b8-2b6d-437e-b053-d51dbf6af163.png</url>
      <title>DEV Community: Charles Zhang</title>
      <link>https://dev.to/methodoxdivooka</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/methodoxdivooka"/>
    <language>en</language>
    <item>
      <title>DevLog 20261002: Desktop Automation Toolbox and Mouse Emulation</title>
      <dc:creator>Charles Zhang</dc:creator>
      <pubDate>Fri, 02 Oct 2026 15:15:03 +0000</pubDate>
      <link>https://dev.to/methodox/devlog-20261002-desktop-automation-toolbox-and-mouse-emulation-1113</link>
      <guid>https://dev.to/methodox/devlog-20261002-desktop-automation-toolbox-and-mouse-emulation-1113</guid>
      <description>&lt;p&gt;I remember when I used to use Macro Recorder to automate simple repetitive tasks — I especially liked its macro recording feature. It requires no programming and can easily get tedious jobs involving clicking and waiting done.&lt;/p&gt;

&lt;p&gt;Since then, I've long moved past automating with basic mouse/keyboard emulation and have sunk deep into an API-driven approach — I just love how clean everything is when you can really program it. Well, that's a major reason why I started Divooka in the first place — lots of ecosystems and lots of tools we use are broken beyond repair and inherently NOT designed to be automated cleanly. &lt;a href="https://medium.methodox.io/opinion-some-reflections-on-mcp-4d4d24d76779" rel="noopener noreferrer"&gt;MCP&lt;/a&gt; partly addressed that problem, but it's really just a modern remedy for mediocre design.&lt;/p&gt;

&lt;p&gt;Either way, when I set out to implement procedural context for Divooka, desktop automation is nonetheless one of the first things that comes to mind. As we get more mature in &lt;a href="https://dev.to/methodox/devlog-20260319-towards-oop-document-level-main-module-and-module-level-members-and-behaviors-133b"&gt;OOP programming&lt;/a&gt;, &lt;a href="https://dev.to/methodox/devlog-20250710-generics-in-divooka-49e6"&gt;generics&lt;/a&gt;, and recently achieved a true &lt;a href="https://lnkd.in/p/d2UyzBVw" rel="noopener noreferrer"&gt;data structure algorithm implementation&lt;/a&gt; in Divooka, that is drawing closer.&lt;/p&gt;

&lt;p&gt;In this &lt;a href="https://youtu.be/x8_Qjephn4o" rel="noopener noreferrer"&gt;short video&lt;/a&gt;, I show a desktop example for the automation toolbox that will come with the upcoming &lt;a href="https://store.steampowered.com/app/4422920/Divooka_Explore/" rel="noopener noreferrer"&gt;Divooka Explore&lt;/a&gt;. It's small, but it shows quite a few interesting things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It creates a countdown GUI using the Immediate GUI framework.&lt;/li&gt;
&lt;li&gt;It interacts with an opened window — in this case, the Paint program — through keyboard shortcuts and mouse input.&lt;/li&gt;
&lt;li&gt;It can parse a stroke-based font at runtime and draw text with it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you ask me, that's a lot of cool features in such a small example! Really excited, and there's still more work to do, so see you soon!&lt;/p&gt;

</description>
      <category>divooka</category>
      <category>automation</category>
      <category>toolbox</category>
      <category>divookaexplore</category>
    </item>
    <item>
      <title>DevLog 20260919: Upcoming Fantasy Planet Painter 2.0 and the Making of It</title>
      <dc:creator>Charles Zhang</dc:creator>
      <pubDate>Sat, 19 Sep 2026 14:29:27 +0000</pubDate>
      <link>https://dev.to/methodox/devlog-20260919-upcoming-fantasy-planet-painter-20-and-the-making-of-it-57gd</link>
      <guid>https://dev.to/methodox/devlog-20260919-upcoming-fantasy-planet-painter-20-and-the-making-of-it-57gd</guid>
      <description>&lt;h1&gt;
  
  
  Fantasy Planet Painter
&lt;/h1&gt;

&lt;p&gt;Whole-planet-scale fictional world-building: continents, mountain ranges, rivers, valleys, and features as small as individual hills. Maybe eventually trees, buildings, houses, roads, and cities too?&lt;/p&gt;

&lt;p&gt;That has always been roughly the idea behind Fantasy Planet Painter (FPP).&lt;/p&gt;

&lt;p&gt;Making a good-looking fantasy map is one part of the goal, but the longer-term idea is more ambitious: making an entire fictional planet something you can actually author, organize, inspect, render, and use.&lt;/p&gt;

&lt;p&gt;With the upcoming Fantasy Planet Painter 2.0, we are finally starting to build more seriously toward that idea.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Beginning
&lt;/h2&gt;

&lt;p&gt;FPP had a rather humble origin: I needed to create a world map.&lt;/p&gt;

&lt;p&gt;Naturally, I started with GIS software. First ArcGIS, then QGIS, and along the way I learned my way through map projections, layers, coordinate systems, GeoServers, and all the other fun stuff that comes with GIS.&lt;/p&gt;

&lt;p&gt;GIS is incredibly powerful, but for making a fictional planet from scratch, the workflow was not particularly efficient. I wasn't really trying to analyze an existing world; I wanted to sit down and &lt;em&gt;make one&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Eventually I came across Torben Mogensen's &lt;code&gt;planet&lt;/code&gt; program (&lt;code&gt;planet.exe&lt;/code&gt; on Windows). It was rather eye-opening because the program works on an entire spherical planet at once instead of treating the world as just another flat image.&lt;/p&gt;

&lt;p&gt;The underlying terrain generator uses recursive tetrahedral subdivision to produce planetary terrain, and it can render the generated planet using many different cartographic projections. That became the technical foundation from which FPP originally grew. We later ported the original code to C# and gradually modernized and extended it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Start
&lt;/h2&gt;

&lt;p&gt;The first practical problem was obvious: &lt;code&gt;planet.exe&lt;/code&gt; is essentially a command-line program, and custom maps are represented through text-based input.&lt;/p&gt;

&lt;p&gt;That is powerful if you already know exactly what you want to type, but not particularly convenient if you are trying to &lt;em&gt;draw&lt;/em&gt; a world.&lt;/p&gt;

&lt;p&gt;Before FPP, I even made several small command-line utilities for operations such as moving and scaling cells around. It technically worked, but authoring by repeatedly modifying parameters, rendering an image, looking at the result, and trying again was painfully inefficient.&lt;/p&gt;

&lt;p&gt;So the first major goal became exposing all of this through a much more usable graphical interface.&lt;/p&gt;

&lt;p&gt;That took much longer than expected.&lt;/p&gt;

&lt;p&gt;The original code is quite old and was never designed with this kind of interactive editing workflow in mind. Adding a GUI was therefore not simply a matter of putting some buttons around it. Rendering, state management, editing, visualization, save files, projections, cross-platform support, and eventually performance all had to be integrated around the original generator.&lt;/p&gt;

&lt;p&gt;Version 1.0 was really the beginning of that process rather than its end.&lt;/p&gt;

&lt;p&gt;Over time we added higher-resolution authoring, more output options, brushes, polygon editing, region selection, map themes, improved rendering controls, and substantial performance work. By v1.4, techniques we had been developing for Divooka - erosion, terrain processing, procedural coloring, texturing, and rendering - also started finding their way back into FPP.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Road Map
&lt;/h2&gt;

&lt;p&gt;Once you start thinking seriously about authoring an &lt;em&gt;entire planet&lt;/em&gt;, a lot of other problems immediately appear.&lt;/p&gt;

&lt;p&gt;Some of the major ones are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Location and region management&lt;/li&gt;
&lt;li&gt;Local and regional maps&lt;/li&gt;
&lt;li&gt;Countries, cultures, biomes, settlements, etc.&lt;/li&gt;
&lt;li&gt;Different levels of detail&lt;/li&gt;
&lt;li&gt;Map rendering and map serving&lt;/li&gt;
&lt;li&gt;Exporting the same world into other tools and applications&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is why I don't really think of FPP as only a procedural map generator.&lt;/p&gt;

&lt;p&gt;A nice-looking map is important, of course. But a fictional world can easily contain thousands of named places and many completely different scales of information. A mountain range visible on a world map may contain hundreds of valleys, rivers, lakes, roads, towns, and individual terrain features that should not even exist at the resolution of the original world map.&lt;/p&gt;

&lt;p&gt;That creates an interesting authoring problem.&lt;/p&gt;

&lt;p&gt;A single giant bitmap is clearly not the answer. At the same time, splitting everything into unrelated maps loses the fact that they all belong to the same world.&lt;/p&gt;

&lt;p&gt;Our original public roadmap already described Version 2 as &lt;strong&gt;Advanced World Editing&lt;/strong&gt;, beginning with location management, followed eventually by better brushes, color control, biome editing, 3D worlds, roads, and cities. The exact implementation has changed quite a lot since that roadmap was written, but the overall direction has not.&lt;/p&gt;

&lt;p&gt;The important part is that FPP needs to gradually move from "a program that generates a picture of a planet" toward "a program that understands the things making up that planet."&lt;/p&gt;

&lt;h2&gt;
  
  
  Feedback and Improvements
&lt;/h2&gt;

&lt;p&gt;There haven't been many direct comments on the Steam community page so far. FPP is still a small and fairly niche product.&lt;/p&gt;

&lt;p&gt;Refund information, however, gives us another source of feedback, and the pattern is fairly understandable: people want more power, more detailed editing, and more features.&lt;/p&gt;

&lt;p&gt;None of those are particularly surprising.&lt;/p&gt;

&lt;p&gt;They correspond closely to limitations we were already aware of in Version 1.x. FPP became quite good at quickly establishing the large-scale shape of a planet, but there is a substantial gap between sketching continents and actually authoring the world &lt;em&gt;inside&lt;/em&gt; those continents.&lt;/p&gt;

&lt;p&gt;That feedback has helped us decide which parts of the longer-term roadmap should move closer to the front.&lt;/p&gt;

&lt;h2&gt;
  
  
  Version 2.0
&lt;/h2&gt;

&lt;p&gt;There are three big areas we want to address at the beginning of Version 2.0:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;World regions&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A new custom spherical tetrahedral-subdivision system with real-world physical units&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Major mapping, editing, theming, and rendering improvements&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first one is perhaps the most important structurally.&lt;/p&gt;

&lt;p&gt;Regions give us a way to start treating parts of the world as actual objects rather than just pixels or height values. Once regions exist properly, a lot of other ideas become much easier to build around them: locations, countries, biome areas, local maps, selections, metadata, exports, and eventually much more detailed world-building information.&lt;/p&gt;

&lt;p&gt;The second area goes much deeper into FPP's original foundation.&lt;/p&gt;

&lt;p&gt;The new subdivision system is also closely tied to another important change in 2.0: &lt;strong&gt;the metric system&lt;/strong&gt;. FPP now operates using real physical units, with distances and elevations represented in meters rather than abstract map values. A planet can have an Earth-scale radius, mountains can genuinely be several kilometres high, and distances between locations can be measured and discussed precisely.&lt;/p&gt;

&lt;p&gt;This sounds like a relatively small change, but it makes world-building much more practical. Terrain, regions, local maps, roads, settlements, and eventually buildings can all exist within the same physical frame of reference instead of relying on arbitrary scales that change depending on context.&lt;/p&gt;

&lt;p&gt;The tetrahedral subdivision approach has served FPP very well, but we are now experimenting with our own spherical subdivision implementation and data structures. Having control over this level of the system lets us build that real-world scale directly into the geometry while also making it easier to integrate editing, generation, resolution changes, region handling, and future algorithms without constantly working around assumptions inherited from the original renderer.&lt;/p&gt;

&lt;p&gt;And finally there is everything users actually interact with every day: mapping, editing, theming, and rendering.&lt;/p&gt;

&lt;p&gt;Version 1.x accumulated many useful features, sometimes rather experimentally. Version 2 gives us a chance to reorganize some of those ideas around a more coherent model instead of continuously extending what was originally a relatively simple world-map painter.&lt;/p&gt;

&lt;p&gt;These three areas should provide a much better foundation for extending FPP going forward.&lt;/p&gt;

&lt;h2&gt;
  
  
  The W2B Project
&lt;/h2&gt;

&lt;p&gt;A surprisingly useful side project in all of this has been &lt;strong&gt;World to Beyond&lt;/strong&gt;, or W2B.&lt;/p&gt;

&lt;p&gt;It was originally proposed mostly out of curiosity.&lt;/p&gt;

&lt;p&gt;I sometimes play &lt;a href="https://www.beyondallreason.info/" rel="noopener noreferrer"&gt;Beyond All Reason&lt;/a&gt; with my friend. Well, nowadays he is so good - even though I was the one who first got him into the game - that he no longer plays with me 😟.&lt;/p&gt;

&lt;p&gt;At some point he looked at FPP and said that its maps kind of looked like BAR maps, so: couldn't FPP make one?&lt;/p&gt;

&lt;p&gt;Visually, the answer seemed like it should be "yes."&lt;/p&gt;

&lt;p&gt;Technically, of course, it was considerably more complicated.&lt;/p&gt;

&lt;p&gt;BAR uses the Spring RTS engine, and producing an actual playable map means much more than exporting a nice-looking heightmap. Terrain needs to work from a gameplay perspective. You have textures, metal distribution, grass, water, features, map metadata, engine-specific formats, reachability, and all kinds of details that don't really matter when the final output is only an image.&lt;/p&gt;

&lt;p&gt;So we made a &lt;a href="https://youtu.be/JDSvBHWeLQg?si=LWabE7djm2_9qVHb" rel="noopener noreferrer"&gt;first attempt&lt;/a&gt;, but never really published much around it.&lt;/p&gt;

&lt;p&gt;People on Discord also quickly pointed out some obvious weaknesses, particularly the lack of convincing erosion and proper texturing.&lt;/p&gt;

&lt;p&gt;Conveniently, those were exactly the kinds of problems we had independently started working on in &lt;a href="https://store.steampowered.com/app/4770820/Divooka_Explore_Demo/" rel="noopener noreferrer"&gt;Divooka&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Over the past year, our terrain work in Divooka has expanded from basic noise generation into erosion, hydrology analysis, masks, terrain coloration, texturing, and eventually PBR terrain rendering. FPP has already started benefiting from some of that work.&lt;/p&gt;

&lt;p&gt;So when we returned to W2B, we were in a rather different position.&lt;/p&gt;

&lt;p&gt;The new W2B has now been rewritten from scratch. Earlier prototypes required users to provide a template extracted from an existing map, which was awkward both technically and from a redistribution/licensing perspective. The current implementation writes the core Spring map files - &lt;code&gt;.smf&lt;/code&gt;, &lt;code&gt;.smt&lt;/code&gt;, and &lt;code&gt;mapinfo.lua&lt;/code&gt; - itself.&lt;/p&gt;

&lt;p&gt;The authoring process has also grown into several distinct stages: source terrain, terrain setup, metal, grass, water, features, and final output. In other words, it is slowly becoming an actual map-making pipeline rather than an FPP export button.&lt;/p&gt;

&lt;p&gt;And this experiment is useful for FPP itself.&lt;/p&gt;

&lt;p&gt;It forces us to deal with questions that don't appear when creating a world map purely for viewing: Is this terrain navigable? What happens when we look much closer? Where does vegetation belong? How do large-scale shapes survive when converted into local terrain? How should a procedural world turn into something another application can actually &lt;em&gt;use&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;Those questions are very relevant to where we want Fantasy Planet Painter to go.&lt;/p&gt;

&lt;p&gt;With all the work that has gone into FPP, Divooka, procedural terrain generation, and the first W2B experiment, we are much better equipped this time around.&lt;/p&gt;

&lt;p&gt;And the new W2B is already much more powerful - and, importantly, much prettier - than the original attempt.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fymgqsmdubrri7x4ronde.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fymgqsmdubrri7x4ronde.png" alt="Methodox Fantasy Planet Painter World to Beyond Plugin for BAR Map Making - Procedural" width="800" height="559"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7a077hbwmi36x2bx6hl8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7a077hbwmi36x2bx6hl8.png" alt="Methodox Fantasy Planet Painter World to Beyond Plugin for BAR Map Making - Reachability" width="800" height="559"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F54db600r0ujubex3ojvi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F54db600r0ujubex3ojvi.png" alt="Methodox Fantasy Planet Painter World to Beyond Plugin for BAR Map Making - Grass Map" width="800" height="559"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Going Forward
&lt;/h2&gt;

&lt;p&gt;Fantasy Planet Painter started from a relatively simple problem: I wanted a convenient way to draw a fictional world map.&lt;/p&gt;

&lt;p&gt;That problem turned out to contain a surprisingly large number of smaller problems.&lt;/p&gt;

&lt;p&gt;Some are mathematical. Some are GIS problems. Some are rendering problems. Some are UI problems. And increasingly, some are really data-modeling problems: how do you represent an entire fictional world in a way that remains editable from the scale of a planet down to individual regions - and where those scales correspond to actual physical measurements?&lt;/p&gt;

&lt;p&gt;With 2.0, an Earth-sized world is actually Earth-sized, mountains can be described in kilometres, and distances between places have a concrete meaning. That gives the rest of the world-building system a much more practical foundation.&lt;/p&gt;

&lt;p&gt;Version 1.x gave us a functional planet painter.&lt;/p&gt;

&lt;p&gt;Version 2.0 is where we start taking the "world-building" part much more seriously.&lt;/p&gt;

&lt;p&gt;There is still a lot to figure out, and some of the ideas described here will certainly change as we implement them. But the pieces developed over the past year - especially the work on terrain generation in Divooka and experiments such as World to Beyond - are starting to connect together rather nicely.&lt;/p&gt;

&lt;p&gt;We will share more as 2.0 takes shape.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://fantasyplanetpainter.com/" rel="noopener noreferrer"&gt;Fantasy Planet Painter Website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fantasyplanetpainter.com/road-map/index.html" rel="noopener noreferrer"&gt;Original FPP Roadmap&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://store.steampowered.com/app/3336490/Fantasy_Planet_Painter/" rel="noopener noreferrer"&gt;Fantasy Planet Painter on Steam&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=_-83stjP5D0" rel="noopener noreferrer"&gt;Fantasy Planet Painter YouTube&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wiki.methodox.io/Utilities/FantasyPlanetPainter/index.html" rel="noopener noreferrer"&gt;Methodox Wiki – Fantasy Planet Painter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wiki.methodox.io/Utilities/FantasyPlanetPainter/WorldToBeyond/" rel="noopener noreferrer"&gt;Methodox Wiki – World to Beyond&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/methodox/devlog-20260731-fpp-v140-and-divooka-explore-demo-updates-45fc"&gt;DevLog 20260731: FPP v1.4.0 and Divooka Explore Demo Updates&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://store.steampowered.com/news/app/3336490/view/704405656884478635" rel="noopener noreferrer"&gt;Steam Announcement&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>fantasyplanetpainter</category>
      <category>worldbulding</category>
      <category>methodox</category>
      <category>rpg</category>
    </item>
    <item>
      <title>DevLog 20260731: FPP v1.4.0 and Divooka Explore Demo Updates</title>
      <dc:creator>Charles Zhang</dc:creator>
      <pubDate>Sat, 01 Aug 2026 18:43:48 +0000</pubDate>
      <link>https://dev.to/methodox/devlog-20260731-fpp-v140-and-divooka-explore-demo-updates-45fc</link>
      <guid>https://dev.to/methodox/devlog-20260731-fpp-v140-and-divooka-explore-demo-updates-45fc</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;The past few months have seen several &lt;a href="https://store.steampowered.com/app/4770820/Divooka_Explore_Demo/" rel="noopener noreferrer"&gt;Divooka Explore Demo&lt;/a&gt; releases, with our efforts focused on rolling out procedural terrain generation features. These include random noise-based terrain generation, procedural and simulation-based terrain processing, natural effects such as erosion and hydrology analysis, procedural coloration and texturing, and, more recently, improved 3D previews of generated terrains directly within the Divooka visual programming environment.&lt;/p&gt;

&lt;p&gt;One small lesson from this cumulative effort is that there always seems to be more academic material, papers, tutorials, online examples, and even source code snippets to discover and use to improve existing systems. That is a shiny silver lining, since we are still fairly new to this domain and desperately need broad exposure to existing techniques.&lt;/p&gt;

&lt;p&gt;Another useful lesson is that, although many papers approach these subjects from a real-time rendering perspective - such as the old GPU Gems articles - the same techniques are often equally applicable in a pure-CPU regime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Procedural Terrain Generation in Divooka
&lt;/h2&gt;

&lt;p&gt;As mentioned, we have made quite a large number of functions available in this area, and the subject definitely warrants much more detailed treatment. Documentation and comprehensive video guides are dearly needed.&lt;/p&gt;

&lt;p&gt;To recap, we have moved from very basic noise-based terrain generation to custom heterogeneous multifractal terrains, functional erosion - although there is still plenty of room for improvement - various masking techniques, practical procedural coloring using either masks or SQL queries, and preliminary texturing support.&lt;/p&gt;

&lt;p&gt;The terrain generation is only the first step, and there is still plenty of room for improvement despite we have achieved useful basic implementation for now.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcfby2rzz6npluz7m2gao.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcfby2rzz6npluz7m2gao.png" alt="Early Days of Divooka Procedural Terrain Generation" width="799" height="259"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnhyt4ucgj8rmt6j6dj66.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnhyt4ucgj8rmt6j6dj66.png" alt="Early Days of Heightmap Erosion in Divooka" width="800" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once we became comfortable with generating functional procedural terrains, we could move on to coloring, texturing, and eventually shading.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3fhglcp8ua1qcjihp57d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3fhglcp8ua1qcjihp57d.png" alt="Procedural Terrain Coloring Nodes in Divooka" width="799" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxp37pt7t8lxifbs3fure.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxp37pt7t8lxifbs3fure.png" alt="Procedural Terrain Coloring in Divooka" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this point, I realized a fundamental limitation of heightmaps and image textures. They may be able to represent distant views of terrains spanning 8–64 km when viewed from above, but for ground-level rendering, procedural shading techniques are a must.&lt;/p&gt;

&lt;p&gt;The 3D rendering work is still new, and there is a long way to go. We started with basic baked shadows, moved on to experimental texture mixing, and eventually reached full PBR rendering.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdbnaxh38qz0bwuaxn5rn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdbnaxh38qz0bwuaxn5rn.png" alt="Baked Shadow Mapping for Terrains in Divooka" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx6krjxvmpq3v290dptfp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx6krjxvmpq3v290dptfp.png" alt="Early days of Procedural Texturing of Terrains with Photo and Generated Textures" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvhchudakvcdy7246vxg1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvhchudakvcdy7246vxg1.png" alt="Voxel Space Viewer of Textured Terrains in Divooka" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;PBR rendering is the holy grail. Once we figure out how to dynamically populate terrains with trees, grass, and rocks - and generate ripples across reflective water - we will be able to share all of it through Divooka as reproducible workflows!&lt;/p&gt;

&lt;p&gt;Below is an intermediate result so far. It uses simple slope-based texturing, which we plan to replace with mask-layer-based materials in the next attempt.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqaqttl67bkfriv8dwdag.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqaqttl67bkfriv8dwdag.png" alt="Procedural Slope-Based Textures for Procedural Terrains in Divooka, PBR Rendering" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have learned so much and cannot wait to share more about the specific techniques once we have more spare time!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fantasy Planet Painter v1.4.0 Update
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnnmnixcs4ix5ak6n51sc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnnmnixcs4ix5ak6n51sc.png" alt="2D Flat World in FPP - Basic Altitude Coloring to Relief Render" width="799" height="259"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While the eventual goal is to support whole-planet procedural generation and content management, we began with basic Earth-like world map generation. At the moment, our work is mostly focused on improving the existing editing experience. The previous update significantly improved the multithreaded generation performance of the tetrahedral subdivision method.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdfu4tmp9x88fflt9s00r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdfu4tmp9x88fflt9s00r.png" alt="Fantasy Planet Painter by Methodox" width="799" height="259"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The 2D cell-based height-value canvas is an interesting medium. It allows us to sketch the desired elevation levels quickly at a high level while the program procedurally populates the remaining details.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgjsmj34ou5pku8f0irjx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgjsmj34ou5pku8f0irjx.png" alt="Painting on a 2D Cell Map with Abstract Height Values While FPP Generate Procedural Details" width="799" height="515"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There has also been some experimental exploration into using this medium for direct 2D terrain editing, as seen in our previous video, &lt;a href="https://youtu.be/JDSvBHWeLQg?si=MEAZdVL4sYn674m-" rel="noopener noreferrer"&gt;World to Beyond (BAR map export from FPP)&lt;/a&gt;. Although BAR export is still in its early stages, the potential of this kind of authoring technique is indeed interesting.&lt;/p&gt;

&lt;p&gt;Following the advances in procedural terrain generation within Divooka, we have gathered quite a collection of useful tools and made them available at a high level in the latest &lt;a href="https://store.steampowered.com/news/app/3336490/view/697645818486718921" rel="noopener noreferrer"&gt;FPP update&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6qcq7dpm5jgriw2vlsxs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6qcq7dpm5jgriw2vlsxs.png" alt="Hydrology (Erosion) Outputs from Divooka" width="800" height="537"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The results are very interesting, and I am glad that we are moving beyond practical utility toward generating worlds that - at least in my eyes - are also visually appealing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0nxgi0fyxujhgfuslf1m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0nxgi0fyxujhgfuslf1m.png" alt="2D Flat World in FPP - 1K Wetness + Slope-Based Relief Render" width="799" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;That is a brief recap for today. I have been busy diving headfirst into all the nitty-gritty work, which has left less time for DevLog updates and for sharing the techniques and progress behind them.&lt;/p&gt;

&lt;p&gt;Still, let us look forward to the day when new results are ready to shine, when we are comfortable placing an early-access version of Divooka into the hands of users, and when we can talk in much greater detail about the road that led us there!&lt;/p&gt;

</description>
      <category>divooka</category>
      <category>fpp</category>
      <category>fantasyplanetpainter</category>
      <category>procedural</category>
    </item>
    <item>
      <title>Designing a Binary Data API for Divooka with PPM and PLY Examples</title>
      <dc:creator>Charles Zhang</dc:creator>
      <pubDate>Tue, 12 May 2026 20:14:30 +0000</pubDate>
      <link>https://dev.to/methodox/working-with-binary-data-in-divooka-55im</link>
      <guid>https://dev.to/methodox/working-with-binary-data-in-divooka-55im</guid>
      <description>&lt;p&gt;Revision: 001&lt;br&gt;&lt;br&gt;
Last Update: 2026-05-12&lt;/p&gt;

&lt;p&gt;One thing I feel is not discussed enough in typical programming conversations is working with binary data. It often seems as though there is a ready-to-use library for whatever data type we need. But that illusion breaks whenever we want to do something new, interface with a less common format, work with legacy data, or build high-performance applications around custom data formats.&lt;/p&gt;

&lt;p&gt;Designing a custom data format that fits the application at hand is an important skill. In this post, however, we will focus less on the philosophy and more on the operational details.&lt;/p&gt;

&lt;p&gt;Binary formats are everywhere: images, meshes, save files, network packets, custom asset bundles, compressed streams, hardware protocols, game data, and many old file formats that are still useful decades later. Yet in application code, binary parsing often becomes messy very quickly. You start with a &lt;code&gt;byte[]&lt;/code&gt;, then an offset integer, then a few helper methods, then nested offset calculations. Before long, the parser has two responsibilities at once: understanding the file format and manually managing where the cursor is.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ptedroqt0rgdz8iz5u7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ptedroqt0rgdz8iz5u7.png" alt="File Container Formats" width="800" height="349"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;File Container Formats &lt;a href="https://upload.wikimedia.org/wikipedia/commons/a/ab/Container_Formats_Examples.svg" rel="noopener noreferrer"&gt;WikiCommons&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Design Motivation
&lt;/h2&gt;

&lt;p&gt;At a higher abstraction level, the goal of Divooka is to move cursor-management logic into something closer to &lt;a href="https://kaitai.io/" rel="noopener noreferrer"&gt;Kaitai Struct&lt;/a&gt; or DFDL, the Data Format Description Language. This makes it easier to work with standardized, well-documented formats such as &lt;a href="https://en.wikipedia.org/wiki/Resource_Interchange_File_Format" rel="noopener noreferrer"&gt;RIFF&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Instead of writing code like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;magic&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ASCII&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BinaryPrimitives&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadInt32LittleEndian&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsSpan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BinaryPrimitives&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadInt32LittleEndian&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsSpan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we want this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImmutableByteArrayReader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Data.bin"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;magic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is similar in spirit to a stream-based API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BinaryReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ASCII&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;leaveOpen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;magic&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ASCII&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadInt32&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadInt32&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The involved types here are &lt;code&gt;ImmutableByteArrayReader&lt;/code&gt;, &lt;code&gt;MutableByteArrayWriter&lt;/code&gt;, and &lt;code&gt;ImmutableByteArrayWriter&lt;/code&gt;, all exposed through a fluent API. The parser reads like the binary format itself.&lt;/p&gt;

&lt;p&gt;The writer mirrors that idea:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MutableByteArrayWriter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteStringFixedSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DATA"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The idea is simple: binary data should be represented as a sequence of explicit read and write operations, not as scattered offset arithmetic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhcq83jbk0t3csy7qh6lt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhcq83jbk0t3csy7qh6lt.png" alt="Binary Data Reading and Writing in Divooka" width="800" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading Binary Data
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Binary Buffer Reader: &lt;code&gt;ImmutableByteArrayReader&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ImmutableByteArrayReader&lt;/code&gt; is a reader-oriented abstraction over binary data.&lt;/p&gt;

&lt;p&gt;It is immutable. Reading from it does not mutate the current object. Instead, read operations return the remaining unread portion of the buffer.&lt;/p&gt;

&lt;p&gt;The main API shape is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ImmutableByteArrayReader&lt;/span&gt; &lt;span class="nf"&gt;ReadInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;ReadInt&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;ReadIntWithOffset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;byteIndex&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These three forms cover three common use cases.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;out&lt;/code&gt; form is for fluent parsing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;magic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The direct-return form is for reading a value from the start of a buffer when the remaining data is not needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadInt&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The offset form is for random access without consuming anything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadIntWithOffset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives the type this clear behavior: Sequential reads consume by returning a new remaining buffer. Offset reads do not consume. Because the reader is immutable, slicing can be cheap: reading a subrange can return another reader over the same underlying data instead of copying bytes.&lt;/p&gt;

&lt;p&gt;That means this kind of operation is cheap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;ImmutableByteArrayReader&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both &lt;code&gt;remaining&lt;/code&gt; and &lt;code&gt;header&lt;/code&gt; can be views over the same internal byte array. Since neither object can mutate the backing storage, this remains safe.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reading blocks
&lt;/h3&gt;

&lt;p&gt;Binary formats often have fixed-size blocks. For example, a file might contain a 32-byte header, followed by 32 bytes of padding, followed by records.&lt;/p&gt;

&lt;p&gt;The API shape for bytes is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ImmutableByteArrayReader&lt;/span&gt; &lt;span class="nf"&gt;ReadBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;ImmutableByteArrayReader&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ImmutableByteArrayReader&lt;/span&gt; &lt;span class="nf"&gt;ReadBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ImmutableByteArrayReader&lt;/span&gt; &lt;span class="nf"&gt;ReadBytesWithOffset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;byteIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ImmutableByteArrayReader&lt;/span&gt; &lt;span class="nf"&gt;ReadBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;ImmutableByteArrayReader&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ImmutableByteArrayReader&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;continuation&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The continuation form is useful when a sub-block should be parsed separately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;versionNumber&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImmutableByteArrayReader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"MyData.bin"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;magic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;ImmutableByteArrayReader&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadUInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;versionNumber&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main stream advances by 32 bytes, while the continuation reads from the isolated header block.&lt;/p&gt;

&lt;p&gt;This is especially useful for formats where blocks have fixed sizes, but only some fields inside the block are currently relevant.&lt;/p&gt;

&lt;h3&gt;
  
  
  Strings
&lt;/h3&gt;

&lt;p&gt;Strings usually appear in binary formats in two common forms.&lt;/p&gt;

&lt;p&gt;The first is fixed-size:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ImmutableByteArrayReader&lt;/span&gt; &lt;span class="nf"&gt;ReadString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;ReadString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;ReadStringWithOffset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;byteIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, a file magic value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;magic&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second is zero-terminated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ImmutableByteArrayReader&lt;/span&gt; &lt;span class="nf"&gt;ReadString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;ReadString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;ReadStringWithOffset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;byteIndex&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This reads until the first &lt;code&gt;0x00&lt;/code&gt; byte.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fixed-size strings are common in headers. Zero-terminated strings are common in older binary formats, mesh files, embedded metadata, and C-style structures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing Binary Data
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Binary Buffer Writer: &lt;code&gt;MutableByteArrayWriter&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;MutableByteArrayWriter&lt;/code&gt; is the mutable writer.&lt;/p&gt;

&lt;p&gt;It is a fluent append-only byte builder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MutableByteArrayWriter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteStringFixedSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CBA1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteDouble&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3.14159&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is mutable in-place. Each call appends bytes to the same underlying buffer and returns &lt;code&gt;this&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This makes it the practical default for producing binary files.&lt;/p&gt;

&lt;p&gt;The API deliberately uses explicit method names:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;WriteInt&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;span class="nf"&gt;WriteUInt&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;span class="nf"&gt;WriteDouble&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;span class="nf"&gt;WriteStringFixedSize&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;span class="nf"&gt;WriteStringZeroTerminated&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;span class="nf"&gt;WriteBytes&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This avoids the ambiguity of a single overloaded &lt;code&gt;Write(...)&lt;/code&gt; method. In binary code, explicitness is usually worth the extra characters.&lt;/p&gt;

&lt;h3&gt;
  
  
  Immutable writer: &lt;code&gt;ImmutableByteArrayWriter&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ImmutableByteArrayWriter&lt;/code&gt; mirrors the writer API, but every write returns a new object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;baseFile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ImmutableByteArrayWriter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteStringFixedSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"TEST"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pathA&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;baseFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteStringZeroTerminated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Variant A"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pathB&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;baseFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteStringZeroTerminated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Variant B"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful when experimenting with different downstream write paths.&lt;/p&gt;

&lt;p&gt;For example, you may have a common header, then several possible payload layouts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;common&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ImmutableByteArrayWriter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteStringFixedSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"MESH"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;triangleMesh&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;common&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteStringZeroTerminated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"triangles"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;triangleCount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pointCloud&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;common&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteStringZeroTerminated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"points"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pointCount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mutable writer is faster and more memory efficient. The immutable writer is better for branching, testing, and functional construction patterns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Writing fixed-size blocks
&lt;/h3&gt;

&lt;p&gt;Writers also support fixed-size subblocks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;MutableByteArrayWriter&lt;/span&gt; &lt;span class="nf"&gt;WriteBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;MutableByteArrayWriter&lt;/span&gt; &lt;span class="nf"&gt;WriteBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MutableByteArrayWriter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;subblock&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;MutableByteArrayWriter&lt;/span&gt; &lt;span class="nf"&gt;WriteBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MutableByteArrayWriter&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the mutable version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;versionNumber&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;MutableByteArrayWriter&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MutableByteArrayWriter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteStringFixedSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CBA1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;con&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;con&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteUInt&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;uint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;versionNumber&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
    &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;128&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Copy1.bin"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Copy2.bin"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The block continuation writes into a temporary sub-buffer. If it writes fewer than 32 bytes, the result is automatically padded with zeroes. If it writes more than 32 bytes, the writer should throw.&lt;/p&gt;

&lt;p&gt;This makes fixed-size binary structures much easier to express.&lt;/p&gt;

&lt;p&gt;The immutable version uses a functional continuation instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;ImmutableByteArrayWriter&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ImmutableByteArrayWriter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteStringFixedSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CBA1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;con&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;con&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteUInt&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;uint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;versionNumber&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Internally, that continuation should be shaped as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ImmutableByteArrayWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ImmutableByteArrayWriter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;because immutable writes return new objects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Example: reading a PPM image
&lt;/h3&gt;

&lt;p&gt;PPM is a simple image format from the Netpbm family. It is useful as a teaching format because the structure is small and direct. The examples below intentionally implement a minimal binary P6 reader and writer, rather than a complete Netpbm parser.&lt;/p&gt;

&lt;p&gt;A binary PPM file starts with an ASCII header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;P6
width height
maxValue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;followed by a single whitespace byte and then the binary RGB pixel data.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;P6
256 256
255
&amp;lt;binary RGB bytes&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The header is text, but the pixel payload is binary. This makes PPM a good example of mixed text and binary parsing. In the common 8-bit case where &lt;code&gt;maxValue&lt;/code&gt; is &lt;code&gt;255&lt;/code&gt;, the payload is &lt;code&gt;width * height * 3&lt;/code&gt; bytes. PPM also allows comments in the header and larger sample values, but this example deliberately supports only simple comment-free P6 files with &lt;code&gt;maxValue == 255&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A simple PPM image type might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PpmImage&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Width&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Height&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;Pixels&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;PpmImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;pixels&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Width&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;Height&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;Pixels&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pixels&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This simplified parser supports only the common 8-bit RGB form. General P6 files can use larger sample values and may contain comments in the header, so a production parser would need to handle those cases before reading the raster payload. Using &lt;code&gt;ImmutableByteArrayReader&lt;/code&gt;, a basic parser could be written as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;PpmImage&lt;/span&gt; &lt;span class="nf"&gt;ReadPpm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ImmutableByteArrayReader&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImmutableByteArrayReader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ReadAsciiToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;magic&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;magic&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;"P6"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidDataException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Only binary P6 PPM files are supported."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ReadAsciiToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;widthText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ReadAsciiToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;heightText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ReadAsciiToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;maxValueText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;widthText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;heightText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;maxValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;maxValueText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ConsumeSingleAsciiWhitespace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Skip delimiter whitespace&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;maxValue&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidDataException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Only 8-bit PPM files with max value 255 are supported."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;pixelByteCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pixelByteCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;ImmutableByteArrayReader&lt;/span&gt; &lt;span class="n"&gt;pixelData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PpmImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pixelData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToArray&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;ImmutableByteArrayReader&lt;/span&gt; &lt;span class="nf"&gt;ConsumeSingleAsciiWhitespace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ImmutableByteArrayReader&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;IsAsciiWhitespace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidDataException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Expected whitespace before PPM raster data."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Skip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The helper token reader can be implemented as a small routine over the immutable buffer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;ImmutableByteArrayReader&lt;/span&gt; &lt;span class="nf"&gt;ReadAsciiToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ImmutableByteArrayReader&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;SkipAsciiWhitespace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;IsAsciiWhitespace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
        &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;++;&lt;/span&gt;

    &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ASCII&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsSpan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Skip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;ImmutableByteArrayReader&lt;/span&gt; &lt;span class="nf"&gt;SkipAsciiWhitespace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ImmutableByteArrayReader&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;IsAsciiWhitespace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
        &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;++;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Skip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;IsAsciiWhitespace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="sc"&gt;' '&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="sc"&gt;'\t'&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="sc"&gt;'\r'&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="sc"&gt;'\n'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This parser is intentionally minimal: it demonstrates the cursor-management pattern without implementing every detail of the Netpbm format. It does not support comments, alternate sample sizes, or multiple concatenated images in one file.&lt;/p&gt;

&lt;p&gt;The parser is not fighting offsets. Each helper receives a buffer and returns the remaining buffer. That is the central pattern.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: writing a PPM image
&lt;/h3&gt;

&lt;p&gt;Writing a binary PPM file is straightforward with &lt;code&gt;MutableByteArrayWriter&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;WritePpm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ReadOnlySpan&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;rgbPixels&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;expectedLength&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rgbPixels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;expectedLength&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Expected &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;expectedLength&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; RGB byte(s)."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rgbPixels&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MutableByteArrayWriter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteStringFixedSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"P6\n"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteStringFixedSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteByte&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="sc"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteStringFixedSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteByte&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="sc"&gt;'\n'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteStringFixedSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"255\n"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rgbPixels&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This writes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;P6
width height
255
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then appends raw RGB bytes.&lt;/p&gt;

&lt;p&gt;The string methods are still explicit. We are not pretending the whole file is text. We are building a binary byte sequence with some ASCII sections.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcijf2psqvnpb6rsvk3oh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcijf2psqvnpb6rsvk3oh.png" alt="Writing Out Binary PPM P6 Format Image in Divooka and Viewing using GIMP" width="800" height="564"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can open a &lt;code&gt;.ppm&lt;/code&gt; image with GIMP.&lt;/p&gt;

&lt;p&gt;Contrast this with a traditional stream-based version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;WritePpm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ReadOnlySpan&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;rgbPixels&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;expectedLength&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rgbPixels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;expectedLength&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Expected &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;expectedLength&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; RGB byte(s)."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rgbPixels&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;FileStream&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;BinaryWriter&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BinaryWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ASCII&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;leaveOpen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ASCII&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"P6\n"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ASCII&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
    &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="sc"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ASCII&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
    &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="sc"&gt;'\n'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ASCII&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"255\n"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rgbPixels&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code is command-oriented, whereas &lt;strong&gt;the fluent writer makes the data structure visually continuous&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: reading a PLY file
&lt;/h3&gt;

&lt;p&gt;PLY is a common polygon mesh format. It is especially visible today because of its use in workflows such as &lt;a href="https://en.wikipedia.org/wiki/Gaussian_splatting" rel="noopener noreferrer"&gt;Gaussian Splatting&lt;/a&gt;. It can be ASCII or binary. A binary little-endian PLY file starts with an ASCII header, followed by binary element data.&lt;/p&gt;

&lt;p&gt;The important detail is that PLY is property-driven. The header declares the elements, their counts, and the properties stored for each element. The binary body then stores those declared properties in order. A complete PLY reader should therefore parse the element/property layout and read or skip fields according to the header.&lt;/p&gt;

&lt;p&gt;For this article, we will use a deliberately small subset of binary little-endian PLY: vertices containing only &lt;code&gt;float x&lt;/code&gt;, &lt;code&gt;float y&lt;/code&gt;, and &lt;code&gt;float z&lt;/code&gt;, followed by faces containing only &lt;code&gt;property list uchar int vertex_indices&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A simplified binary PLY header for that subset might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ply
format binary_little_endian 1.0
element vertex 3
property float x
property float y
property float z
element face 1
property list uchar int vertex_indices
end_header
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the binary body follows.&lt;/p&gt;

&lt;p&gt;For that header, the body contains three vertices, each with three 32-bit floats:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;x y z
x y z
x y z
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then one face:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uchar count
int index0
int index1
int index2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A small model type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;struct&lt;/span&gt; &lt;span class="nf"&gt;Vertex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;Y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;Z&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PlyMesh&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Vertex&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Vertices&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Vertex&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;]&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Faces&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;]&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A simplified binary PLY reader for exactly this layout can be written as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;PlyMesh&lt;/span&gt; &lt;span class="nf"&gt;ReadBinaryLittleEndianPly&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ImmutableByteArrayReader&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImmutableByteArrayReader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ByteArrayEndianness&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LittleEndian&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ASCII&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ReadPlyHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;vertexCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;faceCount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;mesh&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PlyMesh&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;vertexCount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Vertices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Vertex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;faceCount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadByte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt; &lt;span class="n"&gt;indexCount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;indices&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;indexCount&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;indexCount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
            &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="n"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Faces&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The header reader can remain text-oriented:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;ImmutableByteArrayReader&lt;/span&gt; &lt;span class="nf"&gt;ReadPlyHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ImmutableByteArrayReader&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;vertexCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;faceCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;vertexCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;faceCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ReadAsciiLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"end_header"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StringSplitOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RemoveEmptyEntries&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"element"&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"vertex"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;vertexCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"element"&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"face"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;faceCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;ImmutableByteArrayReader&lt;/span&gt; &lt;span class="nf"&gt;ReadAsciiLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ImmutableByteArrayReader&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="sc"&gt;'\n'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;++;&lt;/span&gt;

    &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ASCII&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsSpan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;TrimEnd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'\r'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;++;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Skip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, the binary body parser is clean because the cursor is implicit in the returned &lt;code&gt;ImmutableByteArrayReader&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This reader is not a general-purpose PLY parser. It assumes the exact layout shown above: three &lt;code&gt;float&lt;/code&gt; properties for each vertex and one &lt;code&gt;uchar&lt;/code&gt;/&lt;code&gt;int&lt;/code&gt; list property for each face. Real PLY files often include additional vertex properties such as normals, colors, texture coordinates, opacity, or Gaussian Splatting attributes. They may also use different scalar names or different element layouts. A complete reader would parse the full header schema, then read or skip each declared property in order.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: writing a binary PLY file
&lt;/h3&gt;

&lt;p&gt;Writing a simple triangle mesh to binary little-endian PLY is similarly direct. This writer emits the same restricted layout used by the reader: each vertex has three 32-bit floats, and each face has a &lt;code&gt;uchar&lt;/code&gt; count followed by 32-bit integer vertex indices.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;WriteBinaryLittleEndianPly&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IReadOnlyList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Vertex&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;vertices&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IReadOnlyList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;]&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;faces&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MutableByteArrayWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ByteArrayEndianness&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LittleEndian&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ASCII&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteStringFixedSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ply\n"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteStringFixedSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"format binary_little_endian 1.0\n"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteStringFixedSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"element vertex &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;vertices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;\n"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteStringFixedSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"property float x\n"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteStringFixedSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"property float y\n"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteStringFixedSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"property float z\n"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteStringFixedSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"element face &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;faces&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;\n"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteStringFixedSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"property list uchar int vertex_indices\n"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteStringFixedSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"end_header\n"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Vertex&lt;/span&gt; &lt;span class="n"&gt;vertex&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;vertices&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vertex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vertex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vertex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Z&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;face&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;faces&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;face&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidDataException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PLY face has too many indices for uchar list count."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteByte&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;face&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;face&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
            &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;face&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A triangle can then be written like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;vertices&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Vertex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Vertex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Vertex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;faces&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nf"&gt;WriteBinaryLittleEndianPly&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Triangle.ply"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vertices&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;faces&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code reads as the layout of the file, not as a sequence of stream or array mutations.&lt;/p&gt;

&lt;p&gt;Below is what the code would look like with traditional &lt;code&gt;BinaryWriter&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;WriteBinaryLittleEndianPly&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IReadOnlyList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Vertex&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;vertices&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IReadOnlyList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;]&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;faces&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;FileStream&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;BinaryWriter&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BinaryWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ASCII&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;leaveOpen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ASCII&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ply\n"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ASCII&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"format binary_little_endian 1.0\n"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ASCII&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"element vertex &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;vertices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;\n"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ASCII&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"property float x\n"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ASCII&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"property float y\n"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ASCII&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"property float z\n"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ASCII&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"element face &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;faces&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;\n"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ASCII&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"property list uchar int vertex_indices\n"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ASCII&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"end_header\n"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Vertex&lt;/span&gt; &lt;span class="n"&gt;vertex&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;vertices&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vertex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vertex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vertex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Z&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;face&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;faces&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;face&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidDataException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PLY face has too many indices for uchar list count."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;face&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;face&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
            &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;face&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The PLY writer is still fairly readable, but it becomes more procedural - the stream owns the cursor, and the code is expressed as side effects against that stream.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Reflection on the Design
&lt;/h2&gt;

&lt;p&gt;The reader and writer APIs are intentionally symmetrical.&lt;/p&gt;

&lt;p&gt;Reader:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;ReadInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;ReadFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;ReadString&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;span class="nf"&gt;ReadBytes&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Writer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;WriteInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;WriteFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;WriteString&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;WriteBytes&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reader consumes and returns remaining data. The writer appends and returns the current or new builder.&lt;/p&gt;

&lt;p&gt;The result is a high-level binary workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImmutableByteArrayReader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Input.bin"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;magic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;ImmutableByteArrayReader&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MutableByteArrayWriter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteStringFixedSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;magic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes binary code easier to inspect, easier to test, and easier to refactor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Procedural vs Dataflow Use: Mutable vs Immutable Construction
&lt;/h2&gt;

&lt;p&gt;There are two writer types because they solve different problems.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;MutableByteArrayWriter&lt;/code&gt; is for ordinary construction. It is mutable, append-only, efficient, and can be saved multiple times.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MutableByteArrayWriter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteStringFixedSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"FILE"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"A.bin"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"B.bin"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ImmutableByteArrayWriter&lt;/code&gt; is for branching construction.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;common&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ImmutableByteArrayWriter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteStringFixedSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"FILE"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;common&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteStringZeroTerminated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"A"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;common&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteStringZeroTerminated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"B"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The immutable writer is not meant to replace the mutable one for high-throughput output. It exists for safety, experimentation, and forkable construction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Future Improvements
&lt;/h2&gt;

&lt;p&gt;The API can be improved in several directions.&lt;/p&gt;

&lt;p&gt;A schema layer could sit on top of these primitives. Instead of manually writing every field, a format could define named fields, block sizes, validations, and version-specific layouts.&lt;/p&gt;

&lt;p&gt;The reader could gain richer parsing helpers, such as &lt;code&gt;ReadAsciiToken&lt;/code&gt;, &lt;code&gt;ReadAsciiLine&lt;/code&gt;, &lt;code&gt;ReadUntil&lt;/code&gt;, &lt;code&gt;TryRead&lt;/code&gt;, and &lt;code&gt;Peek&lt;/code&gt; variants. These would make mixed text/binary formats like PPM and PLY even cleaner.&lt;/p&gt;

&lt;p&gt;The writer could support reservation and backpatching. Many binary formats write a length or offset before the final value is known. A controlled API for placeholders would make this safe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;lengthPosition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReserveInt&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="p"&gt;...&lt;/span&gt;

&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PatchInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lengthPosition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payloadLength&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For very large files, both reader and writer could have stream-backed variants. &lt;code&gt;ImmutableByteArrayReader&lt;/code&gt; is ideal for in-memory data, but some workflows need lazy reading, memory-mapped files, or chunked output.&lt;/p&gt;

&lt;p&gt;The immutable writer could also use a rope-like internal representation instead of copying the whole byte array on every append. That would preserve functional semantics while making branching construction much more efficient.&lt;/p&gt;

&lt;p&gt;The core idea, though, should stay the same: binary data should be worked with as a fluent sequence of explicit operations. The API should make the structure of the format visible, while hiding the repetitive mechanics of offset movement, byte ordering, padding, and raw byte appending.&lt;/p&gt;

</description>
      <category>divooka</category>
      <category>binary</category>
      <category>fileformat</category>
      <category>api</category>
    </item>
    <item>
      <title>A Soft Introduction to EasyShell</title>
      <dc:creator>Charles Zhang</dc:creator>
      <pubDate>Tue, 05 May 2026 02:30:47 +0000</pubDate>
      <link>https://dev.to/methodox/introducing-easyshell-65g</link>
      <guid>https://dev.to/methodox/introducing-easyshell-65g</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;"Can it be simpler?" - A recurring thought I've had while working on build automation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://github.com/MethodoxTech/EasyShell" rel="noopener noreferrer"&gt;EasyShell&lt;/a&gt; (command: &lt;code&gt;easy&lt;/code&gt;) is a minimal shell scripting language designed from the ground up for &lt;strong&gt;simplicity and cross-platform build/process automation&lt;/strong&gt;. If you’ve ever felt that PowerShell’s syntax is powerful but easy to forget, or that traditional shell scripts get messy across operating systems, EasyShell aims to strike a cleaner balance.&lt;/p&gt;

&lt;p&gt;It's implemented in C# and runs on .NET, giving you natural access to the entire .NET ecosystem while keeping the scripting experience lightweight and approachable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design Principles
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;One command per line (shell-like flow)&lt;/li&gt;
&lt;li&gt;Very small set of core concepts&lt;/li&gt;
&lt;li&gt;Strongly-typed global variables&lt;/li&gt;
&lt;li&gt;Seamless .NET interop via reflection&lt;/li&gt;
&lt;li&gt;Expressions in parentheses for inline evaluation&lt;/li&gt;
&lt;li&gt;Easy to read, write, and remember&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://github.com/MethodoxTech/EasyShell/releases/tag/v0.1.0" rel="noopener noreferrer"&gt;Version &lt;strong&gt;0.1.0&lt;/strong&gt;&lt;/a&gt; is now available — functional for everyday automation tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Language Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Variables
&lt;/h3&gt;

&lt;p&gt;All variables are declared with an explicit type and live in global scope:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INTVAR Count 42
BOOLVAR Debug TRUE
DOUBLEVAR Ratio 3.14
STRINGVAR Message "Hello from EasyShell"
HANDLEVAR Now System.DateTime.Now   # stores object instances
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Variable names are &lt;strong&gt;case-insensitive&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Reference with &lt;code&gt;$VarName&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Re-assign with &lt;code&gt;$VarName = value&lt;/code&gt; (or expressions)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Expressions (The Power Feature)
&lt;/h3&gt;

&lt;p&gt;Wrap sub-commands in parentheses to evaluate them inline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;STRINGVAR Formatted (System.String.Format "Count = {0}" $Count)
BOOLVAR IsBig (&amp;gt; $Count 100)
STRINGVAR Today (format "{0:yyyy-MM-dd}" (GetDate))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Calling .NET
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Static members&lt;/strong&gt; (fully qualified):
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  System.Console.WriteLine "Hello World!"
  System.IO.File.WriteAllText "output.txt" $Message
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Instance methods&lt;/strong&gt; via HANDLE:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  HANDLEVAR Now System.DateTime.Now
  STRINGVAR Stamp (CALL $Now ToString)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Control Flow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IF (== $Count 42)
    print "The answer!"
ELSEIF (&amp;gt; $Count 50)
    print "Too high"
ELSE
    print "Something else"
END

INTVAR I 0
WHILE (&amp;lt; $I 5)
    System.Console.WriteLine $I
    $I = (+ $I 1)   # arithmetic helpers coming soon
END
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Functions
&lt;/h3&gt;

&lt;p&gt;Simple named blocks (no parameters — use globals):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FUNC BuildProject
    print "Building..."
    dotnet build
END

CALL BuildProject
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  External Commands
&lt;/h3&gt;

&lt;p&gt;Any command that isn’t a keyword or .NET call falls back to executing an external process (with stdout capture):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git status
dotnet publish ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Real-World Example
&lt;/h2&gt;

&lt;p&gt;Check out the full build script used by the project itself:&lt;br&gt;&lt;br&gt;
&lt;strong&gt;&lt;a href="https://github.com/MethodoxTech/EasyShell/blob/main/BuildScripts/BuildEasyShell.easy" rel="noopener noreferrer"&gt;BuildEasyShell.easy&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It demonstrates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Path handling and folder creation&lt;/li&gt;
&lt;li&gt;Running &lt;code&gt;dotnet publish&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Conditional logic for OS/architecture&lt;/li&gt;
&lt;li&gt;Creating a dated zip package&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This script is self-contained and shows how EasyShell can manage non-trivial automation tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Start
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# REPL mode&lt;/span&gt;
easy

&lt;span class="c"&gt;# Run a script&lt;/span&gt;
easy path/to/script.easy

&lt;span class="c"&gt;# Help&lt;/span&gt;
easy &lt;span class="nt"&gt;--help&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A dedicated &lt;strong&gt;VS Code syntax highlighting extension&lt;/strong&gt; is available: &lt;a href="https://marketplace.visualstudio.com/items?itemName=Methodox.easyshell" rel="noopener noreferrer"&gt;Methodox.easyshell&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Who Is It For?
&lt;/h2&gt;

&lt;p&gt;EasyShell is ideal for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build automation scripts&lt;/li&gt;
&lt;li&gt;Simple CI/CD glue&lt;/li&gt;
&lt;li&gt;Daily developer tooling&lt;/li&gt;
&lt;li&gt;Anyone who wants .NET power without heavy dependency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's intentionally minimal — no advanced Bash-style features, just the essentials done in a way that’s hard to forget.&lt;/p&gt;

&lt;p&gt;Give it a try and let me know what you think!&lt;/p&gt;

</description>
      <category>automation</category>
      <category>cicd</category>
      <category>shell</category>
      <category>scripting</category>
    </item>
    <item>
      <title>DevLog 20251214: Yet Another Shell Script – Keeping Build Automation Simple with EasyShell</title>
      <dc:creator>Charles Zhang</dc:creator>
      <pubDate>Tue, 05 May 2026 02:23:04 +0000</pubDate>
      <link>https://dev.to/methodox/devlog-20251214-yet-another-shell-script-keeping-build-automation-simple-with-easyshell-587i</link>
      <guid>https://dev.to/methodox/devlog-20251214-yet-another-shell-script-keeping-build-automation-simple-with-easyshell-587i</guid>
      <description>&lt;p&gt;As we roll out more tools, both internal and public, there has been a growing need for easy-to-maintain shell scripting and build automation. Build automation is complex enough without all the syntax burden - my favorite is to use PowerShell, but often it feels like wrestling with the tool itself. PowerShell is powerful, sure, but its depth often means hunting down syntax I &lt;em&gt;know&lt;/em&gt; I've used a dozen times before. Bash has its quirks across platforms, and honestly I don't know good ways of using it outside MinGW, Git Bash, or WSL - none of which felt quite "right".&lt;/p&gt;

&lt;p&gt;I wanted something lighter: a shell scripting tool that's &lt;strong&gt;easy to pick up, cross-platform, and hard to forget&lt;/strong&gt; once you start using it.&lt;/p&gt;

&lt;p&gt;So I made &lt;strong&gt;EasyShell&lt;/strong&gt; (or just &lt;code&gt;easy&lt;/code&gt;): a minimal shell scripting language implemented in C# that focuses on process automation with a very small set of concepts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Motivation and Design
&lt;/h2&gt;

&lt;p&gt;EasyShell is for "lazy" developers (like me) who just want to automate common tasks - calling .NET APIs, running external tools, handling simple logic - without a steep syntax curve or platform-specific headaches. It combines a shell-like one-command-per-line flow with strong typing and easy .NET interop. No advanced Bash-style wizardry; just straightforward scripting.&lt;/p&gt;

&lt;p&gt;It could also serve as a nice companion alongside other tools in the long-term Divooka ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Taste of the Syntax
&lt;/h2&gt;

&lt;p&gt;Files typically use the &lt;code&gt;.easy&lt;/code&gt; extension, with syntax highlighting available via the &lt;a href="https://marketplace.visualstudio.com/items?itemName=Methodox.easyshell" rel="noopener noreferrer"&gt;VS Code extension&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here's a small sample from the repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Comments start with #
HANDLEVAR NOW System.DateTime.Now

# Instance method via CALL
STRINGVAR DATE (CALL $NOW ToString)

# Static .NET call with expression
STRINGVAR VALUE (System.String.Format "Current time: {0}" $DATE)

STRINGVAR PATH "C:/temp/greeting.txt"
System.IO.File.WriteAllText $PATH $VALUE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Variables are declared with their type (&lt;code&gt;INTVAR&lt;/code&gt;, &lt;code&gt;STRINGVAR&lt;/code&gt;, &lt;code&gt;BOOLVAR&lt;/code&gt;, &lt;code&gt;DOUBLEVAR&lt;/code&gt;, &lt;code&gt;HANDLEVAR&lt;/code&gt;), names are case-insensitive, and everything lives in global scope for simplicity. Expressions are wrapped in parentheses for inline evaluation, like &lt;code&gt;(== $X 10)&lt;/code&gt; or sub-commands.&lt;/p&gt;

&lt;p&gt;Control flow sticks to the basics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;IF ... ELSEIF ... ELSE ... END&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WHILE ... END&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Simple &lt;code&gt;FUNC ... END&lt;/code&gt; / &lt;code&gt;CALL&lt;/code&gt; (globals make "returns" easy via variables)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can call external executables naturally, and non-built-in commands fall through to process execution (stdout capture included).&lt;/p&gt;

&lt;h2&gt;
  
  
  Early Days
&lt;/h2&gt;

&lt;p&gt;This is version 0.1.0 territory - functional for everyday build tasks, with room to grow (arithmetic helpers, more examples, etc.). You can run it in REPL mode (&lt;code&gt;easy&lt;/code&gt;) or point it at a script. Builds and packages are straightforward with the included scripts.&lt;/p&gt;

&lt;p&gt;If you've ever wished for something between a full PowerShell script and a quick batch file that "just works" across machines, maybe give EasyShell a try. The GitHub repo has the full language overview, examples, and build details: &lt;a href="https://github.com/MethodoxTech/EasyShell" rel="noopener noreferrer"&gt;MethodoxTech/EasyShell&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>automation</category>
      <category>cicd</category>
      <category>shell</category>
      <category>scripting</category>
    </item>
    <item>
      <title>DevLog 20260426: Divooka Mandelbrot Benchmark – Putting Our Scripting Language to the Test</title>
      <dc:creator>Charles Zhang</dc:creator>
      <pubDate>Mon, 27 Apr 2026 03:31:48 +0000</pubDate>
      <link>https://dev.to/methodox/devlog-20260426-divookabenchmarkmandelbrot-putting-our-scripting-language-to-the-test-54jf</link>
      <guid>https://dev.to/methodox/devlog-20260426-divookabenchmarkmandelbrot-putting-our-scripting-language-to-the-test-54jf</guid>
      <description>&lt;p&gt;Today we released the first public version of &lt;a href="https://github.com/MethodoxTech/DivookaBenchmark_Mandelbrot" rel="noopener noreferrer"&gt;&lt;strong&gt;DivookaBenchmark_Mandelbrot&lt;/strong&gt;&lt;/a&gt; — a standardized benchmark suite built specifically to measure the real-world performance of Divooka against a wide range of languages and runtimes.&lt;/p&gt;

&lt;p&gt;The benchmark computes the Mandelbrot set at &lt;em&gt;2000 × 2000&lt;/em&gt; resolution with a maximum of 1000 iterations per pixel, then verifies correctness using a 32-bit checksum (target: &lt;code&gt;689833081&lt;/code&gt;). We run each implementation five times and record average time, min/max, peak memory, and distribution size.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why This Benchmark Matters
&lt;/h3&gt;

&lt;p&gt;One of our core beliefs is that &lt;strong&gt;a modern scripting language needs a "decent (high) performance"&lt;/strong&gt;. This benchmark was designed to prove that point in a reproducible way.&lt;/p&gt;

&lt;p&gt;It exercises heavy integer and floating-point math, tight loops, branches, and memory writes — exactly the kind of workload where interpretation overhead shows up quickly. This particular benchmark doesn't test function calls directly.&lt;/p&gt;

&lt;p&gt;The goal is not to produce the an optimized Mandelbrot renderer, but to compare a simple, fixed algorithm across different languages and runtimes, then use the result to understand where Divooka currently stands.&lt;/p&gt;

&lt;p&gt;We included implementations (or equivalent ports) for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;C++&lt;/li&gt;
&lt;li&gt;C# (.NET 10, including AoT/trimmed)&lt;/li&gt;
&lt;li&gt;JavaScript (multiple browsers + Node.js)&lt;/li&gt;
&lt;li&gt;Go, Java, Julia, Python 3.13, Ruby, Prolog, GNU Octave&lt;/li&gt;
&lt;li&gt;And of course, multiple execution modes of Divooka 0.75.2 (&lt;strong&gt;&lt;em&gt;compiled&lt;/em&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;em&gt;Aviator&lt;/em&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;em&gt;Neo Editor&lt;/em&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;em&gt;Stewer&lt;/em&gt;&lt;/strong&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We even tested AI-generated versions from ChatGPT, Gemini, Grok, and Kimi to see how reliable and performant LLM-produced result is in practice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Results (on Windows 11, i7-13700KF)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fastest overall&lt;/strong&gt;: JavaScript in Edge/Chrome/Brave (~1,296 ms, very low memory)😲&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fastest native&lt;/strong&gt;: C++ (~1,308 ms)👍&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strong managed contender&lt;/strong&gt;: C# AoT (~1,355 ms)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Divooka compiled&lt;/strong&gt;: 3,639 ms — comfortably beating pure interpreters like Python (~47 s) and Ruby (~49 s) ⚡&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Divooka Aviator&lt;/strong&gt;: 6,711 ms 🥰&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Divooka Neo Editor&lt;/strong&gt;: significantly higher overhead (estimated multi-hour range for full runs) 🥹&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The gap between Divooka's compiled mode and the top-tier JITs (especially &lt;strong&gt;V8&lt;/strong&gt;) is still noticeable, but the improvement over classic interpreted scripting languages is clear. This reinforces why we’ve been investing heavily in our compilation pipeline and runtime optimizations.&lt;/p&gt;

&lt;p&gt;AI-generated implementations varied wildly — some produced correct checksums but took 26 seconds to several minutes, while others failed entirely or fell back to calling Python under the hood. &lt;strong&gt;Deterministic engineering still wins.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What We Learned
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;JIT is not optional for competitive scripting performance.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Tooling and editor overhead can dominate if not carefully managed (something we're actively addressing).&lt;/li&gt;
&lt;li&gt;Even simple, well-defined tasks like Mandelbrot &lt;em&gt;reveal meaningful differences in language/runtime design&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The full repository is now public:&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/MethodoxTech/DivookaBenchmark_Mandelbrot" rel="noopener noreferrer"&gt;https://github.com/MethodoxTech/DivookaBenchmark_Mandelbrot&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It includes all source implementations, PowerShell automation scripts, result tables, and artifacts. Feel free to clone, run it yourself, or contribute additional language ports.&lt;/p&gt;

&lt;p&gt;This benchmark will become part of our regular performance regression suite as Divooka evolves. Future runs should show steady gains as we refine the compiler and reduce runtime overhead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current Results
&lt;/h2&gt;

&lt;p&gt;Results from best runs of respective platform:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rank&lt;/th&gt;
&lt;th&gt;Language / Runtime&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Best Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;JavaScript Web, Edge / Chrome / Brave&lt;/td&gt;
&lt;td&gt;3 KB&lt;/td&gt;
&lt;td&gt;1295.80–1302.80 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;C++&lt;/td&gt;
&lt;td&gt;15 KB&lt;/td&gt;
&lt;td&gt;1308.00 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;C# AoT&lt;/td&gt;
&lt;td&gt;911 KB&lt;/td&gt;
&lt;td&gt;1355.35 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;C# .NET 10&lt;/td&gt;
&lt;td&gt;16.5 MB&lt;/td&gt;
&lt;td&gt;1357.47 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;JavaScript Node.js&lt;/td&gt;
&lt;td&gt;2 KB + 98.2 MB&lt;/td&gt;
&lt;td&gt;1406.20 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;td&gt;2 KB + 224 MB&lt;/td&gt;
&lt;td&gt;1425.01 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Julia&lt;/td&gt;
&lt;td&gt;2 KB + 1 GB&lt;/td&gt;
&lt;td&gt;1511.20 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;JavaScript Web, Firefox&lt;/td&gt;
&lt;td&gt;3 KB&lt;/td&gt;
&lt;td&gt;1513.00 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Java OpenJDK 17.0.11&lt;/td&gt;
&lt;td&gt;301 MB + small files&lt;/td&gt;
&lt;td&gt;1587.79 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Divooka 0.75.2, Compiled&lt;/td&gt;
&lt;td&gt;71.914 MB&lt;/td&gt;
&lt;td&gt;3639.24 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;Divooka 0.75.2, Aviator Run&lt;/td&gt;
&lt;td&gt;8 KB + 2.16 GB + 776 MB&lt;/td&gt;
&lt;td&gt;6710.84 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;Python 3.13.5&lt;/td&gt;
&lt;td&gt;2 KB + 139 MB&lt;/td&gt;
&lt;td&gt;47127.70 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;Ruby 3.2.2&lt;/td&gt;
&lt;td&gt;1 KB + 907 MB&lt;/td&gt;
&lt;td&gt;49489.82 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;Prolog SWI, optimized&lt;/td&gt;
&lt;td&gt;2 KB + 42.8 MB&lt;/td&gt;
&lt;td&gt;314687.84 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;GNU Octave 7.3.0&lt;/td&gt;
&lt;td&gt;2 KB + 2.07 GB&lt;/td&gt;
&lt;td&gt;2464913.29 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;Divooka 0.75.2, Neo Editor estimate&lt;/td&gt;
&lt;td&gt;8 KB + 2.67 GB&lt;/td&gt;
&lt;td&gt;~95.8 hr&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;This benchmark only tests a narrow case.&lt;/p&gt;

&lt;p&gt;It does not test UI programming, data manipulation, graph execution, external libraries, async workloads, recursion-heavy workloads, or large real-world applications.&lt;/p&gt;

&lt;p&gt;It also does not fully normalize warm-up behavior. Browser JavaScript likely benefits from highly optimized JIT behavior, and other runtimes may have different startup / warm-up costs.&lt;/p&gt;

&lt;p&gt;GNU Octave is probably optimized for matrix-oriented workloads, not naive scalar loops.&lt;/p&gt;

&lt;p&gt;Python is rarely used this way in performance-sensitive numerical code; normally people use NumPy, Numba, Cython, PyPy, or native extensions.&lt;/p&gt;

</description>
      <category>divooka</category>
      <category>benchmark</category>
      <category>programming</category>
      <category>performance</category>
    </item>
    <item>
      <title>English is The Worst Programming Language</title>
      <dc:creator>Charles Zhang</dc:creator>
      <pubDate>Sun, 26 Apr 2026 03:47:34 +0000</pubDate>
      <link>https://dev.to/methodox/english-is-the-worst-programming-language-35oi</link>
      <guid>https://dev.to/methodox/english-is-the-worst-programming-language-35oi</guid>
      <description>&lt;p&gt;In the world of software engineering, we obsess over programming languages: their syntax, their type systems, their performance characteristics, their paradigms. We debate Rust versus Go, Python's readability versus C++'s control, Haskell's purity versus JavaScript's chaos. Yet we rarely acknowledge the elephant in the room — or rather, the language we all use every day that makes every other language look like a model of crystalline precision: &lt;strong&gt;English&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;English is, without question, the worst programming language ever created. It is not merely flawed; it is fundamentally unsuited for the reliable transmission of complex ideas. Anyone who has ever tried to communicate a non-trivial technical concept to another human being knows this instinctively. The ambiguities, the contextual dependencies, the endless opportunities for misinterpretation — English excels at all of them.&lt;/p&gt;

&lt;p&gt;If you have spent any time in meetings, code reviews, specification documents, or even casual Slack threads, you have felt the pain. And if you have any background in mathematics, physics, or formal logic, the contrast becomes almost comically stark.&lt;/p&gt;

&lt;h3&gt;
  
  
  Precision: The Foundation of Reliable Computation (and Communication)
&lt;/h3&gt;

&lt;p&gt;At its core, a good programming language is a formal system designed for &lt;em&gt;unambiguous&lt;/em&gt; execution. Every valid program has exactly one meaning under the language's semantics. A compiler or interpreter will produce the same behavior every time, assuming the same inputs and environment. This is not an accident; it is the entire point.&lt;/p&gt;

&lt;p&gt;Mathematics and physics adopted formal languages for the same reason. When Newton or Leibniz developed calculus, they didn't just wave their hands and say "the rate of change, you know, kinda like speed but for curves." They created notation — symbols, rules of manipulation, axioms — that allowed precise statements like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F09vg4fmhxnhwq3ko9fnv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F09vg4fmhxnhwq3ko9fnv.png" alt="Derivative Example" width="165" height="77"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This expression has one, and only one, interpretation among those who understand the formal system. There is no room for "well, depending on what you mean by &lt;em&gt;derivative&lt;/em&gt;" or "in my experience, sometimes it's 2x plus or minus a bit."&lt;/p&gt;

&lt;p&gt;Physics follows suit. Maxwell's equations in differential form are not poetic suggestions:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fije9ywlr9drmgbsihpsq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fije9ywlr9drmgbsihpsq.png" alt="Maxwell Equation" width="596" height="77"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These are not open to "opinions-based" interpretation. Misunderstand a sign or a vector component, and your electromagnetic device will quite literally fail to function. Engineers and physicists spend years learning to think within these formalisms precisely because natural language is too sloppy for the phenomena they describe.&lt;/p&gt;

&lt;p&gt;Formal languages exist because human natural languages are &lt;em&gt;terrible&lt;/em&gt; at edge cases, scope, and unintended interpretations. We invented predicate logic, set theory, lambda calculus, and programming languages to escape the swamp of ambiguity.&lt;/p&gt;

&lt;p&gt;English, by contrast, is the swamp.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Many Ways English Fails as a Specification Language
&lt;/h3&gt;

&lt;p&gt;Consider something as seemingly simple as a requirement: "The system shall process transactions quickly."&lt;/p&gt;

&lt;p&gt;What does "quickly" mean? Under 100ms? Under 500ms? Does it depend on load? On hardware? On network conditions? Is this a hard guarantee or a soft target? English offers no mechanism to distinguish these.&lt;/p&gt;

&lt;p&gt;Now imagine trying to implement a sorting algorithm based on an English description:&lt;/p&gt;

&lt;p&gt;"Sort the list in ascending order, but put the most important items first, unless they're duplicates, in which case keep the original order for those."&lt;/p&gt;

&lt;p&gt;Good luck. Is "important" defined? What constitutes a duplicate? Does "original order" refer to stable sort semantics? English leaves all of this to intuition, shared context, and — inevitably — arguments in code review.&lt;/p&gt;

&lt;p&gt;Real-world examples abound in software projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Handle errors gracefully" — Does this mean log them? Retry? Notify the user? Crash the process? Return a default value?&lt;/li&gt;
&lt;li&gt;"Make the UI intuitive" — Intuitive to whom? A power user? A complete novice? Someone from a different cultural background?&lt;/li&gt;
&lt;li&gt;"The cache should be consistent" — Eventual consistency? Strong consistency? What about partition tolerance?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are not edge cases. They are the norm. Experienced engineers learn to translate vague English requirements into precise specifications, often using formal tools (user stories with acceptance criteria, UML, TLA+, property-based testing) precisely because English alone is insufficient.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ambiguity at Every Level
&lt;/h3&gt;

&lt;p&gt;English is ambiguous at the lexical, syntactic, semantic, and pragmatic levels.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lexical ambiguity&lt;/strong&gt; (multiple meanings for the same word): "Bank" can mean a financial institution, the side of a river, or to rely on something. Context usually helps, but in technical documents, context can itself be contested.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Syntactic ambiguity&lt;/strong&gt; (parsing issues): Classic example — "I saw the man with the telescope." Did I use the telescope to see the man, or did the man have the telescope? Programming languages avoid this with strict grammar rules and operator precedence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Semantic ambiguity&lt;/strong&gt;: "The function should return the largest value." Largest by what metric? In what ordering? For floating-point numbers, what about NaN or signed zeros?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pragmatic ambiguity&lt;/strong&gt;: What is left unsaid. In a conversation between colleagues who have worked together for years, massive amounts of information are implied. New team members, or an AI trying to implement from a spec, have no access to that shared mental model.&lt;/p&gt;

&lt;p&gt;Compare this to Python, often praised for readability:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even without types, the behavior is unambiguous to anyone who knows Python's semantics. &lt;code&gt;max([])&lt;/code&gt; raises an exception, but here we explicitly handle the empty case. The return type hint further constrains expectations.&lt;/p&gt;

&lt;p&gt;English has no equivalent of type hints, no compiler to catch inconsistencies, no unit tests for prose.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why We Put Up With It (And Why We Shouldn't)
&lt;/h3&gt;

&lt;p&gt;English has undeniable strengths. It is expressive, flexible, and culturally rich. It can convey emotion, nuance, humor, and poetry in ways that formal languages cannot. Shakespeare didn't write in lambda calculus for a reason.&lt;/p&gt;

&lt;p&gt;But when the goal is &lt;em&gt;reliable transmission of executable ideas&lt;/em&gt; — specifications, APIs, requirements, designs — those strengths become liabilities. Flexibility is another word for underspecification.&lt;/p&gt;

&lt;p&gt;Anyone with real experience communicating complex ideas knows the pattern:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write what seems like a clear English description.&lt;/li&gt;
&lt;li&gt;Send it to a colleague.&lt;/li&gt;
&lt;li&gt;Receive back an implementation that is technically correct but completely misses the intent.&lt;/li&gt;
&lt;li&gt;Spend hours in discussion clarifying what was "obviously" meant.&lt;/li&gt;
&lt;li&gt;Repeat.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is why mature software organizations invest heavily in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Precise requirements documents with measurable acceptance criteria&lt;/li&gt;
&lt;li&gt;Formal methods (where the cost justifies it — e.g., aerospace, finance, medical devices)&lt;/li&gt;
&lt;li&gt;Contract testing, property-based testing, and exhaustive specification&lt;/li&gt;
&lt;li&gt;Code as the single source of truth, with comments used sparingly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The very existence of these practices is an admission that English is inadequate.&lt;/p&gt;

&lt;p&gt;In physics and mathematics, we don't tolerate "it's approximately true, in the usual sense." We define terms rigorously. We prove theorems. We build models whose predictions can be falsified experimentally. The precision of the language enables the precision of thought and the reliability of results.&lt;/p&gt;

&lt;p&gt;Programming aspires to the same rigor. That's why we have static type systems, formal verification tools like Coq or Lean, and languages with strong guarantees (memory safety, data-race freedom, etc.).&lt;/p&gt;

&lt;p&gt;English remains the worst because it resists all such discipline. It evolves organically, embraces exceptions, and thrives on interpretation. It is a living, messy, human thing — wonderful for literature, conversation, and persuasion; disastrous for programming.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Humble Remark
&lt;/h3&gt;

&lt;p&gt;We don't need to abandon English. We need to recognize its limitations and use better tools where precision matters.&lt;/p&gt;

&lt;p&gt;Next time you write a technical specification, ask yourself: Could this be misinterpreted in a way that leads to incorrect behavior? If the answer is yes (and it almost always is), add precision — through examples, edge cases, invariants, or even pseudocode.&lt;/p&gt;

&lt;p&gt;Treat English as a high-level, lossy compilation target rather than the final executable specification.&lt;/p&gt;

&lt;p&gt;Because in the end, the computer doesn't care about your opinions, your intent, or what "everyone knows" you meant. It executes precisely what you told it — in a real programming language.&lt;/p&gt;

&lt;p&gt;And humans, despite our intelligence, are not much better than computers when the specification is written in the worst programming language of all: English. &lt;/p&gt;

&lt;p&gt;We just hide our bugs better, and argue about them longer.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>ai</category>
      <category>english</category>
    </item>
    <item>
      <title>DevLog 20260319: Metaprogramming (Teaser)</title>
      <dc:creator>Charles Zhang</dc:creator>
      <pubDate>Thu, 19 Mar 2026 20:16:19 +0000</pubDate>
      <link>https://dev.to/methodox/devlog-20260319-metaprogramming-teaser-4pn9</link>
      <guid>https://dev.to/methodox/devlog-20260319-metaprogramming-teaser-4pn9</guid>
      <description>&lt;h2&gt;
  
  
  Quick Comment
&lt;/h2&gt;

&lt;p&gt;Following &lt;a href="https://dev.to/methodox/devlog-20260319-towards-oop-document-level-main-module-and-module-level-members-and-behaviors-133b"&gt;Document-Level Main Module&lt;/a&gt;, the possibility really opens up...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzpyyqm8nrqta08anl9pf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzpyyqm8nrqta08anl9pf.png" alt="Proper Metaprogramming in Divooka" width="634" height="242"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is very different from our earlier hard-coded "Stats" node.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3eo3c5au5cwonf2pquz7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3eo3c5au5cwonf2pquz7.png" alt="A Hardcoded Graph Stats (Summary) Node" width="434" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>metaprogramming</category>
      <category>advanced</category>
      <category>divooka</category>
    </item>
    <item>
      <title>DevLog 20260319: Towards OOP - Document-Level Default Module and Module-Level Members and Behaviors</title>
      <dc:creator>Charles Zhang</dc:creator>
      <pubDate>Thu, 19 Mar 2026 20:16:08 +0000</pubDate>
      <link>https://dev.to/methodox/devlog-20260319-towards-oop-document-level-main-module-and-module-level-members-and-behaviors-133b</link>
      <guid>https://dev.to/methodox/devlog-20260319-towards-oop-document-level-main-module-and-module-level-members-and-behaviors-133b</guid>
      <description>&lt;h2&gt;
  
  
  Revisiting Flappy Bird – The Pain Point
&lt;/h2&gt;

&lt;p&gt;Remember our Flappy Bird example from mid-2025?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F509yykxr054pazr7crm6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F509yykxr054pazr7crm6.png" alt="The Initialization Sequence for Flappy Bird Game in Divooka Interactive Demo" width="800" height="559"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The long chain of variable initializations was needed for two main reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;No way to declare variables before using them
&lt;/li&gt;
&lt;li&gt;More critically - &lt;strong&gt;no way to define custom data structures&lt;/strong&gt; that could group related values together and be instantiated with a single node.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In retrospective it seems obvious: we just need proper structured data containers. But building the scaffolding to support this properly in a visual, node-based system is a bit more involved.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc044pu9pmmgtppisnts7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc044pu9pmmgtppisnts7.png" alt="Initialize State with A Single Node with Structured Data" width="784" height="227"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Searching for a Solution
&lt;/h2&gt;

&lt;p&gt;I spent a long time trying to bring OOP concepts into Divooka. A huge amount of effort went into DiOS (Divooka Open Standards), with the hope that a complete standard would naturally solve these kinds of problems. But standardization work is slow and deep - just like making Divooka itself feature-complete.&lt;/p&gt;

&lt;p&gt;Then, while working on something completely unrelated and taking a short break from this topic, it suddenly clicked.&lt;/p&gt;

&lt;p&gt;The key insight: &lt;strong&gt;start simple and approach it from a functional/declarative angle first&lt;/strong&gt;, before jumping into inheritance, encapsulation, polymorphism, and all those good stuff.&lt;/p&gt;

&lt;p&gt;I'm really excited - we finally have meaningful progress.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Divooka Way
&lt;/h2&gt;

&lt;p&gt;To claim our goal as "everything is a node" is a bit too strong, but I strongly prefer keeping almost everything on the graph.&lt;/p&gt;

&lt;p&gt;This aligns with how we already handle &lt;a href="https://dev.to/methodox/devlog-20260225-divooka-visual-programming-direct-value-setting-of-compound-inputs-4dej"&gt;structured primitives&lt;/a&gt; - direct value setting of compound inputs. That way, there's still hope that users can drop down to raw node programming when they need full control (and thus keep everything programmable).&lt;/p&gt;

&lt;p&gt;Traditional property panels (like Unreal Blueprints' object definition GUIs) are out - they break the pure graph paradigm.&lt;/p&gt;

&lt;p&gt;Instead, the solution is declarative, similar to how TerraGen works: &lt;strong&gt;the mere presence and connection of certain nodes defines meaning&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdt0uo32f876mkby6xfce.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdt0uo32f876mkby6xfce.png" alt="Custom Module Data Member Definition in Divooka" width="709" height="505"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq5rcgs9pioy63whstj6r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq5rcgs9pioy63whstj6r.png" alt="Declarative Programming in Divooka in the Context of Module Member Definitions" width="287" height="275"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This feels &lt;strong&gt;awesome&lt;/strong&gt; for several reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We can make full use of the 2D graph layout - place things exactly where we want them visually
&lt;/li&gt;
&lt;li&gt;It stays completely coherent with Divooka's graphical paradigm
&lt;/li&gt;
&lt;li&gt;No need for separate "class editor" UIs&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's Been Implemented So Far
&lt;/h2&gt;

&lt;p&gt;Current working pieces:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Graphs can now act as &lt;strong&gt;modules&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Support for &lt;strong&gt;module-level data members&lt;/strong&gt; and &lt;strong&gt;behavior members&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;main module&lt;/strong&gt; tied to the document, with existing graphs serving as associated behaviors&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Module-scoped instance node – in Events context&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4fuz1d4xkpggewvio85i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4fuz1d4xkpggewvio85i.png" alt="Module Scoped Instance Node in Divooka - Events Context" width="800" height="357"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Module-scoped instance node – in Dataflow context&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3it8f0cg1weluxbjb9uq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3it8f0cg1weluxbjb9uq.png" alt="Module Scoped Instance Node in Divooka - Dataflow Context" width="565" height="261"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Top-level module behavior graphs&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F54bhw9ol26pp6265hjri.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F54bhw9ol26pp6265hjri.png" alt="Top Level Module Behavior Member Graphs in Divooka" width="776" height="222"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This immediately unlocks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clean access to (top-level) module instances
&lt;/li&gt;
&lt;li&gt;Shared event and dataflow instance data
&lt;/li&gt;
&lt;li&gt;A unified programming and document model for subgraphs (still a work-in-progress) &lt;/li&gt;
&lt;li&gt;In procedural contexts (&lt;strong&gt;Glaze!&lt;/strong&gt; especially): we now have proper data containers - no more awkward giant variable lists or forced tuples! (Compare to the old Flappy Bird screenshot)
&lt;/li&gt;
&lt;li&gt;Much simpler and more natural state storage/access in &lt;strong&gt;Glaze!&lt;/strong&gt; and procedural graphs&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is a big deal - for a system that's gearing up for real production use, it feels like crossing a major milestone in usefulness and core abstractions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current Limitations &amp;amp; Remaining Work
&lt;/h2&gt;

&lt;p&gt;Still plenty to fix/polish:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A bunch of bugs and edge cases
&lt;/li&gt;
&lt;li&gt;Proper serialization support
&lt;/li&gt;
&lt;li&gt;Smooth context switching between graphs
&lt;/li&gt;
&lt;li&gt;Subgraph consolidation / better nesting UX
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Looking Ahead
&lt;/h2&gt;

&lt;p&gt;This is a solid foundation, but there's still a long road:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full modularization support
&lt;/li&gt;
&lt;li&gt;Modules inside modules (nesting)
&lt;/li&gt;
&lt;li&gt;Module inheritance / composition patterns / polymorphism&lt;/li&gt;
&lt;li&gt;Base DOM exposed as the "main module" for metaprogramming API
&lt;/li&gt;
&lt;li&gt;Detailed documentation: how it all fits together, recommended usage patterns, expected behaviors, pitfalls
&lt;/li&gt;
&lt;li&gt;Module-level scoped instances (deeper/finer control)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Overall - I am very happy with the direction. It feels like Divooka is finally getting the abstraction power it deserves while staying true to its visual, graph-first philosophy.&lt;/p&gt;

&lt;p&gt;I am hyped for what we will achieve.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://wiki.methodox.io/en/Frameworks/Glaze/Tutorials/FlappyBird" rel="noopener noreferrer"&gt;The original flappy bird example&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/methodox/devlog-20250711-flappy-bird-in-divooka-sneak-peak-40j8"&gt;DevLog on flappy bird example&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>divooka</category>
      <category>oop</category>
      <category>devlog</category>
      <category>programming</category>
    </item>
    <item>
      <title>A Detective Story: The Case of The Dead Office Guy (Upcoming Game Release)</title>
      <dc:creator>Charles Zhang</dc:creator>
      <pubDate>Fri, 06 Mar 2026 19:29:11 +0000</pubDate>
      <link>https://dev.to/methodox/a-detective-story-the-case-of-the-dead-office-guy-upcoming-game-release-5529</link>
      <guid>https://dev.to/methodox/a-detective-story-the-case-of-the-dead-office-guy-upcoming-game-release-5529</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;In the quiet town of Baytown, an unremarkable man lies dead under suspicious circumstances. Rumors ripple through the streets. Secrets long buried begin to surface. The whole community is buzzing - some in dread, others gripped by dark fascination.&lt;/p&gt;

&lt;p&gt;A Detective Story is a compact detective simulator that brings generative AI to the heart of interactive fiction.&lt;/p&gt;

&lt;p&gt;Step into the role of detective in this small-scale murder investigation. Interrogate a cast of AI-powered townsfolk, each with their own motives, fragmented memories, and the potential to lie or mislead. Explore the map to uncover physical evidence and hidden clues. Piece it all together before time runs out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Game Guide
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Your mission: Solve the murder as efficiently as possible - minimize days elapsed and questions spent.&lt;/li&gt;
&lt;li&gt;Bringing suspects to the station costs valuable time (days). Choose your interviews with care.&lt;/li&gt;
&lt;li&gt;These are informal interrogations - no formal oaths, no truth serum. Witnesses and suspects may deceive, omit, or hide the truth.&lt;/li&gt;
&lt;li&gt;You have only 3 questions per interview. Make every one count.&lt;/li&gt;
&lt;li&gt;Stay in character for maximum immersion: Ask about alibis, relationships, timelines, strange sightings, and contradictions.&lt;/li&gt;
&lt;li&gt;Cross-check stories, hunt inconsistencies, and link conversations to evidence found on the map.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Hardware Requirements
&lt;/h2&gt;

&lt;p&gt;A Detective Story is lightweight and runs smoothly on modest hardware. It requires at least 10 GB of system RAM (DRAM) - with no heavy VRAM demands.&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>game</category>
      <category>simulation</category>
      <category>mystery</category>
    </item>
    <item>
      <title>DevLog 20260225: Divooka Visual Programming - Direct Value Setting of Compound Inputs</title>
      <dc:creator>Charles Zhang</dc:creator>
      <pubDate>Wed, 25 Feb 2026 17:46:45 +0000</pubDate>
      <link>https://dev.to/methodox/devlog-20260225-divooka-visual-programming-direct-value-setting-of-compound-inputs-4dej</link>
      <guid>https://dev.to/methodox/devlog-20260225-divooka-visual-programming-direct-value-setting-of-compound-inputs-4dej</guid>
      <description>&lt;p&gt;Finally, it arrived! Well, not exactly here yet, but getting closer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9uefmdr5tir0qjdcfyax.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9uefmdr5tir0qjdcfyax.png" alt="Screenshot of Visual Programming in Divooka - Value Assignment of Compound Inputs" width="800" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;Divooka intends to make visual programming simple and accessible - the accessible part is achieved both through wide availability and the general-purpose nature of the programming system, but the &lt;em&gt;simple&lt;/em&gt; part is what we spend lots of effort thinking hard trying to improve.&lt;/p&gt;

&lt;p&gt;In the past, dealing with complex configuration takes the form of either &lt;em&gt;"explosion"&lt;/em&gt; of arguments or dedicated &lt;code&gt;Make XXX&lt;/code&gt; construction functions:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmf6epcnl90nzwn4v3sni.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmf6epcnl90nzwn4v3sni.png" alt="An Example of A Node with Lots of Inputs" width="559" height="363"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhaamwdqh83lph8wphcw4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhaamwdqh83lph8wphcw4.png" alt="An Example of A Node Taking a Struct, Which Needs to be Created from a Node with Lots of Inputs" width="763" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The problem arises due to the direct exposure of all input arguments in visual programming. Both approach makes the canvas look busy, with the former a common approach in existing systems - using a struct sounds good on paper but still requires a complex looking construction node. This is a common problem in visual programming systems, below shows how it's done in Blender and ComfyUI.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp74kj1nn0v5clwya2bp4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp74kj1nn0v5clwya2bp4.png" alt="A Node with Many Inputs in ComfyUI" width="355" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5jkcps43hasucbgc4fjr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5jkcps43hasucbgc4fjr.png" alt="A Node with Many Inputs in Blender" width="505" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You do get used to it after a while, but a busy looking canvas remains a busy-looking canvas. In Blender, you can sort of "collapse" a node:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F51ssein4u54ak1b4x2dp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F51ssein4u54ak1b4x2dp.png" alt="Collapse Nodes in Blender" width="617" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As Blender is constantly improving, we can also now collapse property groups as shown in the earlier screenshot.&lt;/p&gt;

&lt;p&gt;As a syntax purist and (unfortunately) very conscious about my working space, my eyes can't tolerate messiness. I kind of like how Houdini approaches this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6rcfmxoty5i6dt915ue9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6rcfmxoty5i6dt915ue9.png" alt="Nodes in Houdini Are Very Simple and Clean" width="479" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F25jwxr7qrhzxak2u6ylm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F25jwxr7qrhzxak2u6ylm.png" alt="Houdini Makes Use of Property Panels" width="800" height="549"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Essentially they offload all the complexity to the properties panel - this has the huge benefit of making everything look very clean. But the downside is Houdini rely heavily on scripting expressions (like Excel).&lt;/p&gt;

&lt;p&gt;TerraGen and World Machine mixes the approach:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8j6701vpaacarx9406jj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8j6701vpaacarx9406jj.png" alt="A Node Graph in World Machine" width="800" height="245"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffabvq61w0sqm3oiybyxf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffabvq61w0sqm3oiybyxf.png" alt="A Node Graph in TerraGen" width="800" height="774"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In both cases, connections represent flow of terrain data only and additional configurations are offloaded to property panels. In TerraGen especially, this allows those properties to be animated. The downside, once again, is &lt;strong&gt;whatever is offloaded to property panels are no longer programmable using nodes&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Our Approach
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9uefmdr5tir0qjdcfyax.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9uefmdr5tir0qjdcfyax.png" alt="Compound Input Editing Popup in Divooka" width="800" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our proposed solution for Divooka looks like this: fundamentally all properties remain as node inputs, but compound values (especially value types) should just allow "editing in place" through a property panel. If more complex programming is needed, one can easily fall back to &lt;code&gt;Make XXX&lt;/code&gt; nodes again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Future Work
&lt;/h2&gt;

&lt;p&gt;There is still remaining work on type check, hierarchical compound types, arrays, and serialization, and also node rendering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;As usual with all the exciting language features of Divooka we intend to introduce, there are lots of details to take care of here.&lt;/p&gt;

</description>
      <category>divooka</category>
      <category>programminglanguage</category>
      <category>devlog</category>
      <category>design</category>
    </item>
  </channel>
</rss>
