<?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>Building Multilingualism with Claude and Divooka App Platform</title>
      <dc:creator>Charles Zhang</dc:creator>
      <pubDate>Thu, 22 Oct 2026 19:21:39 +0000</pubDate>
      <link>https://dev.to/methodox/building-multilingualism-with-claude-and-divooka-app-platform-587b</link>
      <guid>https://dev.to/methodox/building-multilingualism-with-claude-and-divooka-app-platform-587b</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;There is an interesting theory in linguistics, namely &lt;strong&gt;the arbitrariness of the linguistic sign&lt;/strong&gt;: there is no inherent or necessary connection between a word's sound (the signifier) and its meaning (the signified).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;The arbitrariness of the linguistic sign - a fundamental principle of linguistics associated with Swiss linguist Ferdinand de Saussure - dictates that &lt;em&gt;there is no inherent or necessary connection between a word's sound (the signifier) and its meaning (the signified)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;For example, the same animal is called:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;English: dog&lt;/li&gt;
&lt;li&gt;French: chien&lt;/li&gt;
&lt;li&gt;German: Hund&lt;/li&gt;
&lt;li&gt;Japanese: inu (犬)&lt;/li&gt;
&lt;li&gt;Mandarin: gǒu (狗)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These different sounds refer to essentially the same concept. Nothing about the animal inherently requires any particular pronunciation. Saussure formalized this principle in his Course in General Linguistics (1916).&lt;/p&gt;

&lt;p&gt;However, arbitrariness doesn't mean complete randomness. There are important qualifications, including onomatopoeia (words imitating sounds), sound symbolism (certain sounds being associated with particular qualities), and historical relationships between languages.&lt;/p&gt;

&lt;p&gt;The deeper implication is that meaning is established through social convention rather than through any intrinsic relationship between sound and concept.&lt;/p&gt;

&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;I'm not a language expert. I just enjoy exploring languages, particularly when designing my own fictional languages. Sometimes I want to know how a word or phrase is expressed across different languages, either out of curiosity or simply for inspiration.&lt;/p&gt;

&lt;p&gt;For years, &lt;a href="https://www.indifferentlanguages.com/" rel="noopener noreferrer"&gt;In Different Languages&lt;/a&gt; has been a great resource for this. Enter an English word, and you get translations in over 100 languages. Simple and useful.&lt;/p&gt;

&lt;p&gt;Unfortunately, the website frequently becomes unavailable or unreasonably slow. Sometimes a simple word lookup takes far longer than it should.&lt;/p&gt;

&lt;p&gt;To be clear, I never had a problem with its advertisements. Hosting a website costs money, and I have no objection to reasonable monetization. My frustration was purely about reliability and responsiveness.&lt;/p&gt;

&lt;p&gt;Eventually, I thought: why not build my own?&lt;/p&gt;

&lt;p&gt;There was another motivation. I've been developing &lt;a href="https://divooka.app/" rel="noopener noreferrer"&gt;Divooka App Platform&lt;/a&gt;, and wanted a practical project to test its capabilities. Rather than making another trivial demo, I figured a multilingual dictionary would be a useful real-world test.&lt;/p&gt;

&lt;p&gt;That's how &lt;a href="https://multilingualism.divooka.app/" rel="noopener noreferrer"&gt;Multilingualism&lt;/a&gt; came about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building with Claude and Divooka
&lt;/h2&gt;

&lt;p&gt;The entire application was built with Claude through &lt;strong&gt;Divooka App Platform's MCP connector&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of manually writing the website and processing scripts, I worked with Claude to define the requirements, develop the translation pipeline, implement the interface, and iterate on the results.&lt;/p&gt;

&lt;p&gt;For me, this was as much an experiment in AI-assisted development as it was a dictionary project. I wanted to see how far we could take a complete, usable application through the platform's MCP integration, rather than just generating isolated code snippets.&lt;/p&gt;

&lt;p&gt;The initial concept was straightforward: gather translations from available open sources, fill missing entries where possible, and publish the results through a fast, lightweight website.&lt;/p&gt;

&lt;p&gt;Of course, collecting translations is one thing. Making them reasonably consistent across 108 languages is another.&lt;/p&gt;

&lt;p&gt;Since I'm not a linguist, I didn't want to pretend I could personally verify every translation. Instead, we designed the process around existing dictionary data, explicit source attribution, automated checks, and community corrections.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Translation Dataset
&lt;/h2&gt;

&lt;p&gt;We started with the English &lt;a href="https://en.wiktionary.org/" rel="noopener noreferrer"&gt;Wiktionary&lt;/a&gt;, using structured extracts provided by &lt;a href="https://kaikki.org/" rel="noopener noreferrer"&gt;kaikki.org&lt;/a&gt; through Wiktextract.&lt;/p&gt;

&lt;p&gt;Rather than importing the entire dictionary, Claude helped build a Python pipeline that processes the data in several stages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Extract:&lt;/strong&gt; Stream the Wiktionary JSONL dump, keeping English entries with translation tables, definitions, parts of speech, and relevant metadata.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Select:&lt;/strong&gt; Use &lt;code&gt;wordfreq&lt;/code&gt; to prioritize common English words and phrases, considering translation coverage and normalizing language variants.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fill:&lt;/strong&gt; Identify missing word-language pairs and generate translation jobs for Claude, including the intended meaning and part of speech.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate and merge:&lt;/strong&gt; Compare machine-generated results against hidden Wiktionary control samples, flag uncertain translations, and prioritize Wiktionary when both sources are available.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Publish:&lt;/strong&gt; Generate compact JSON files organized by word and language, along with metadata and source information.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We also use Princeton WordNet to organize words into categories.&lt;/p&gt;

&lt;p&gt;The resulting dataset contains &lt;strong&gt;9,852 English words and phrases across 108 languages&lt;/strong&gt;, representing over a million word-language translation entries. Roughly half come from Wiktionary, with the remaining gaps filled by machine translation.&lt;/p&gt;

&lt;p&gt;One interesting challenge is that translation isn't always one-to-one. A word like "light" can refer to illumination, weight, or colour. Translating the wrong meaning produces perfectly valid words that are completely wrong in context.&lt;/p&gt;

&lt;p&gt;The pipeline therefore considers definitions and senses rather than translating isolated English strings.&lt;/p&gt;

&lt;p&gt;There are also complications involving writing systems, transliteration, dialects, archaic forms, and regional conventions. For example, Simplified versus Traditional Chinese, or Serbian versus Croatian spelling conventions.&lt;/p&gt;

&lt;p&gt;Quite a bit of processing logic goes into these details.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Website Without a Backend
&lt;/h2&gt;

&lt;p&gt;For the website itself, we deliberately kept the architecture simple.&lt;/p&gt;

&lt;p&gt;The frontend uses plain HTML, CSS, and JavaScript. No frontend framework, no build step, and no application server.&lt;/p&gt;

&lt;p&gt;All translation data is stored as JSON in the &lt;a href="https://github.com/MethodoxTech/Multilingualism" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; and served through the jsDelivr CDN. The application fetches only the necessary files, caches requests in memory, and falls back to GitHub-hosted data if the primary source fails.&lt;/p&gt;

&lt;p&gt;There is no reason to query a translation model every time someone looks up a word. The expensive processing is done ahead of time.&lt;/p&gt;

&lt;p&gt;This design also gives us something useful almost for free: &lt;strong&gt;a public JSON API&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example, the following URL retrieves translations for "thank you":&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn.jsdelivr.net/gh/MethodoxTech/Multilingualism@main/data/words/thank_you.json" rel="noopener noreferrer"&gt;https://cdn.jsdelivr.net/gh/MethodoxTech/Multilingualism@main/data/words/thank_you.json&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Developers can also retrieve complete language datasets, such as &lt;code&gt;data/langs/ja.json&lt;/code&gt;, or the full word index. For reproducible applications, CDN URLs can be pinned to a specific Git commit instead of &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There's no API key, authentication, or dedicated backend to maintain. The data is also accessible directly from JavaScript applications through CORS-enabled responses.&lt;/p&gt;

&lt;p&gt;More details are available on the &lt;a href="https://multilingualism.divooka.app/api" rel="noopener noreferrer"&gt;API page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accuracy and Open Contributions
&lt;/h2&gt;

&lt;p&gt;Naturally, machine translation isn't perfect, especially across so many languages.&lt;/p&gt;

&lt;p&gt;We generate translations with specific meanings and language conventions, and use control samples to estimate agreement with existing Wiktionary translations. However, automated checks are not a substitute for native-speaker review.&lt;/p&gt;

&lt;p&gt;That's why we explicitly identify machine-generated translations, including those marked as uncertain, rather than presenting everything as equally authoritative.&lt;/p&gt;

&lt;p&gt;We also preserve the translation sources and publish machine-generated entries as reviewable TSV files.&lt;/p&gt;

&lt;p&gt;If you spot an incorrect or awkward translation, you're welcome to &lt;a href="https://github.com/MethodoxTech/Multilingualism/issues" rel="noopener noreferrer"&gt;open a GitHub issue&lt;/a&gt;. The website provides a correction link that pre-fills the relevant context.&lt;/p&gt;

&lt;p&gt;Corrections to Wiktionary-derived translations are best contributed upstream, so other projects can benefit from them as well.&lt;/p&gt;

&lt;p&gt;The application and pipeline code are MIT-licensed, while the translation data is published under CC BY-SA 4.0.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Multilingualism started from two rather different motivations: my frustration with an unreliable but otherwise useful website, and my curiosity about what Claude could build through Divooka App Platform.&lt;/p&gt;

&lt;p&gt;The result is a small but practical application that I can actually use when exploring languages or inventing my own.&lt;/p&gt;

&lt;p&gt;It's also a useful test of the development platform itself. We went beyond a simple UI demo and built a complete data-processing workflow, a static frontend, and a publicly accessible API through an AI-assisted development process.&lt;/p&gt;

&lt;p&gt;I don't expect the dictionary to be perfect. Language is complicated, and even native speakers sometimes disagree on the most appropriate translation.&lt;/p&gt;

&lt;p&gt;But the project is open. Anyone can inspect the data, report mistakes, suggest improvements, or use the JSON in their own applications.&lt;/p&gt;

&lt;p&gt;And perhaps that's the part I appreciate most: something that began as a personal frustration has become a useful application and a real-world development experiment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it:&lt;/strong&gt; &lt;a href="https://multilingualism.divooka.app/" rel="noopener noreferrer"&gt;https://multilingualism.divooka.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Source code:&lt;/strong&gt; &lt;a href="https://github.com/MethodoxTech/Multilingualism" rel="noopener noreferrer"&gt;https://github.com/MethodoxTech/Multilingualism&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Built with:&lt;/strong&gt; &lt;a href="https://divooka.app/" rel="noopener noreferrer"&gt;https://divooka.app/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>python</category>
      <category>webdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>DevLog: 2026-10-15 A Detective Story - Making It to The Release.</title>
      <dc:creator>Charles Zhang</dc:creator>
      <pubDate>Fri, 16 Oct 2026 02:26:16 +0000</pubDate>
      <link>https://dev.to/methodox/devlog-2026-10-15-a-detective-story-making-it-to-the-release-2d07</link>
      <guid>https://dev.to/methodox/devlog-2026-10-15-a-detective-story-making-it-to-the-release-2d07</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A Detective Story&lt;/strong&gt; is a compact detective simulator that brings natural conversation to the heart of interactive fiction, where you can interrogate NPCs using natural languages - as they are powered by LLMs (well, SLMs to be precise).&lt;/p&gt;

&lt;p&gt;A murder has taken place in the sunlit seaside town of Baytown, where everyone has a story. Question suspects, explore the town for clues, and test their alibis against the evidence. You may have only three days to solve the crime - and there may be more than one murderer!&lt;/p&gt;

&lt;h2&gt;
  
  
  Development Experience
&lt;/h2&gt;

&lt;p&gt;Making a game is very different from building an app - or any other kind of software, for that matter. I don't think I've ever worked on a software project where even the smallest incremental improvement could feel so difficult.&lt;/p&gt;

&lt;p&gt;Ironically, even though engine and gameplay development consumed nearly 80% of the development time, I'd still call it the "easy part" of the whole endeavor. Designing the story, integrating the graphics, and, most importantly, making sure the mystery is actually solvable - those turned out to be challenges that go well beyond the usual technical maneuver.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reception
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://coderlegion.com/12443/a-detective-story-the-case-of-the-dead-office-guy-game-trailer" rel="noopener noreferrer"&gt;initial trailer&lt;/a&gt;, despite its relatively simple graphics by today's standards, felt quite intriguing to me. The idea of fully integrating LLM-powered NPCs into a game is certainly nothing new, but it's still rarely seen in actual games.&lt;/p&gt;

&lt;p&gt;I even told my friends that &lt;em&gt;A Detective Story&lt;/em&gt; might end up being more popular than &lt;a href="https://store.steampowered.com/app/3336490/Fantasy_Planet_Painter/" rel="noopener noreferrer"&gt;Fantasy Planet Painter&lt;/a&gt;. Both are niche titles, but ADS seemed more approachable and appealing to a broader audience.&lt;/p&gt;

&lt;p&gt;Reality, however, told a different story. The game attracted relatively little interest during its first few months, which eventually led me to scale back the time and effort I was putting into getting it released.&lt;/p&gt;

&lt;h2&gt;
  
  
  Revival of Effort
&lt;/h2&gt;

&lt;p&gt;With &lt;a href="https://store.steampowered.com/app/4422920/Divooka_Explore/" rel="noopener noreferrer"&gt;Divooka&lt;/a&gt; approaching maturity and preparing for its public release later this year, it's time to revisit and refine the integrated LLM capabilities within the Divooka ecosystem, particularly features such as RAG and knowledge base management.&lt;/p&gt;

&lt;p&gt;ADS runs on Crime Engine, which itself is built upon components from Divooka, making it a natural testing ground for these capabilities.&lt;/p&gt;

&lt;p&gt;On a more personal note, I also have other game projects in mind for Methodox. However, Project Aegis has made little progress, and &lt;a href="https://methodox.itch.io/wayhome" rel="noopener noreferrer"&gt;Codename Wayhome&lt;/a&gt; remains a prototype. At some point, I really need to get a game across the finish line. Otherwise, I'm not sure I'll have the courage to embark on yet another game project!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Things I Am Still Not Satisfied With
&lt;/h2&gt;

&lt;p&gt;I suppose this is something every creative endeavor struggles with. I wish we could polish the scenes a little more, make the story more vibrant, bring the characters to life, and make our little fictional town feel more realistic and expansive. The list goes on and on. But we can't stay hidden forever. At some point, when things are good enough, we have to let our creations out into the world and see how they fare. Perfection is a difficult goal to chase, and perhaps an impossible one to reach.&lt;/p&gt;

&lt;p&gt;On the model side, I can't honestly say the conversation experience is optimal yet. NPCs can sometimes be overly restrictive or reluctant to reveal information, making it unnecessarily difficult for players to piece together clues and solve the mystery. I suspect the best &lt;strong&gt;long-term solution might be to train a custom model specifically for this kind of interaction&lt;/strong&gt;, perhaps taking inspiration from how Character.AI approaches conversational characters. That's certainly something worth exploring further.&lt;/p&gt;

&lt;h2&gt;
  
  
  Remarks
&lt;/h2&gt;

&lt;p&gt;I'll share more about the technical side of using AI in game development in a future post.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://store.steampowered.com/app/4484570/A_Detective_Story/" rel="noopener noreferrer"&gt;https://store.steampowered.com/app/4484570/A_Detective_Story/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://store.steampowered.com/news/app/4484570/view/684141995253302069" rel="noopener noreferrer"&gt;https://store.steampowered.com/news/app/4484570/view/684141995253302069&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>adetectivestory</category>
      <category>gamedev</category>
      <category>detective</category>
    </item>
    <item>
      <title>Fantasy Planet Painter 2.0 Is Here!</title>
      <dc:creator>Charles Zhang</dc:creator>
      <pubDate>Tue, 13 Oct 2026 17:55:06 +0000</pubDate>
      <link>https://dev.to/methodox/fantasy-planet-painter-20-is-here-5h7m</link>
      <guid>https://dev.to/methodox/fantasy-planet-painter-20-is-here-5h7m</guid>
      <description>&lt;p&gt;&lt;strong&gt;Fantasy Planet Painter 2.0&lt;/strong&gt; is here! This is our biggest update so far: a completely rebuilt application, a new physical terrain system, richer rendering, layered painting, fantasy world layers generation, and a much more capable &lt;strong&gt;World to Beyond&lt;/strong&gt; integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Basics: From a Sketch to a World
&lt;/h2&gt;

&lt;p&gt;In our &lt;a href="https://store.steampowered.com/news/app/3336490/view/704405656884478635" rel="noopener noreferrer"&gt;earlier introduction to 2.0&lt;/a&gt;, we shared a first look at where Fantasy Planet Painter was heading. Now we would like to take a closer look at what you can create with this release.&lt;/p&gt;

&lt;p&gt;Fantasy Planet Painter started with a simple approach to world-map creation: paint the broad shape of a world on a cell grid, and a procedural algorithm generates the details. A creator focuses on where continents, oceans, and mountains belong without having to draw every terrain detail.&lt;/p&gt;

&lt;p&gt;That idea remains at the heart of 2.0. What has changed is how much more you can do around it. Heights can now have a physical meaning. Terrain can be built in layers. A painted planet can become an atlas of countries, cultures, settlements, and roads. And jump into very specific region of a world with additional details. After you are done experimenting with the shape, you can export the map and interactively play in Beyond All Reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  A New Physical Planet
&lt;/h2&gt;

&lt;p&gt;The new &lt;strong&gt;Physical World&lt;/strong&gt; generator works with elevations measured in meters, and provides broader control of flexible height ranges, with 12 levels by default. This gives you finer control over the relationship between your painted terrain, the generated detail, and the sea.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Continuous elevations:&lt;/strong&gt; paint specific heights, from deep ocean floors to mountain peaks, with twelve customizable paint bands to keep everyday painting approachable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Independent sea level:&lt;/strong&gt; explore how changing the water level reshapes your coastlines without repainting the whole planet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Planet size and relief:&lt;/strong&gt; adjust the planet radius and the strength of its terrain, with procedural detail that takes physical scale into account.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seed control:&lt;/strong&gt; keep a result you like or try a different realization of the same broad geography.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Painted terrain alone:&lt;/strong&gt; turn off the added procedural detail when you want to see the heightfield you have authored more directly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terrain operations:&lt;/strong&gt; set, add, multiply, reshape with a power curve, smooth, normalize, terrace, or invert elevations. Operations can target land, water, or the whole canvas, and work with selections; coast preservation is available where appropriate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are also &lt;strong&gt;(experimental)&lt;/strong&gt; &lt;strong&gt;fourteen editable preset terrain recipes&lt;/strong&gt;, including Volcano and Fractious, for exploring different starting shapes. Recipes combine a seed and density with editable generation instructions, can be previewed before applying them, and can be retained with a terrain layer. They are useful both as starting points and as material to paint over.&lt;/p&gt;

&lt;h2&gt;
  
  
  More Ways to Create Your World
&lt;/h2&gt;

&lt;p&gt;A world can feel very different as an atlas, a globe, or a view looking across its surface. 2.0 expands the rendering tools so you can explore those differences without rebuilding your geography.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Physical terrain colouring:&lt;/strong&gt; elevation, slope, and climate views, alongside the familiar classic palettes. Latitude can influence the appearance of the land and polar regions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terrain lighting:&lt;/strong&gt; cast shadows, sky occlusion, adjustable sun direction and height, and shading exaggeration to bring out the shape of the landscape.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A rendered 3D view:&lt;/strong&gt; look toward the terrain from different distances and angles, with atmosphere, ocean, and volumetric clouds. Adjust cloud coverage and height to change the mood of a scene.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Geographic guides:&lt;/strong&gt; latitude and longitude grids, plus a kilometre scale tied to the size of your planet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live previews:&lt;/strong&gt; see updates while painting, with background rendering, progress feedback, and cancellation for longer renders.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A clearer connection between canvas and preview:&lt;/strong&gt; the viewport outline helps you keep track of the area you are looking at when working across projections.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The eleven map projections are the same &lt;strong&gt;Equirectangular, Mercator, Orthographic, Gall–Peters, Mollweide, Interrupted sinusoidal, Stereographic, Gnomonic, Icosahedral, Equal-area azimuthal, and Conical&lt;/strong&gt;. Use them to compare the same world from different perspectives, or choose the one that suits your finished map.&lt;/p&gt;

&lt;h2&gt;
  
  
  Painting, Layers, and More Space to Experiment
&lt;/h2&gt;

&lt;p&gt;The painting workspace has been rebuilt around a more flexible canvas and &lt;strong&gt;terrain layers&lt;/strong&gt;, allowing a non-destructive workflow. This allows you to manage different features and separate terrain changes from exiting shapes, such as trying a different mountain range, or hiding a layer to compare different versions of your geography.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Layer controls:&lt;/strong&gt; add, name, reorder, hide, lock, and remove terrain layers. Transparent areas and the eraser reveal what is underneath.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Direct and shaped painting:&lt;/strong&gt; Point, Box, Circle, and Polygon tools for everything from a single cell to a whole landmass.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Soft and blending brushes:&lt;/strong&gt; shape transitions with Linear, Smoothstep, Quadratic, Cubic, Exponential, or Gaussian falloff.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two kinds of fill:&lt;/strong&gt; fill a connected neighbourhood or replace matching heights across the canvas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Selections:&lt;/strong&gt; isolate part of the map for painting and terrain operations, or move selected geography.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Canvas transformations:&lt;/strong&gt; raise or lower terrain, shift with wrapping or clipping, flip a map, and fill areas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grid controls:&lt;/strong&gt; resize, expand or shrink the canvas, and double or halve its resolution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Painting conveniences:&lt;/strong&gt; zoom and pan, preview the brush footprint, and pick an existing height from the canvas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Undo and redo:&lt;/strong&gt; revisit strokes and other changes through the history, with clearer handling of unsaved work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Several of these tools grew through the 1.x updates. They now sit together in a workspace designed for the larger range of things a 2.0 world can contain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build a Fantasy World to Suit Your Terrain
&lt;/h2&gt;

&lt;p&gt;Once you have a Physical World you like, &lt;strong&gt;world layers&lt;/strong&gt; can develop it into a fantasy atlas. Choose the number of countries and settlements, the map detail, and a presentation style, then generate a world that follows your geography.&lt;/p&gt;

&lt;p&gt;This can add &lt;strong&gt;biomes, rivers, lakes, countries, borders, cultures, religions, provinces, settlements, place names, and roads&lt;/strong&gt;. Building the atlas leaves your painted terrain intact, and rebuilding can be undone if you prefer the previous result.&lt;/p&gt;

&lt;p&gt;Different map presets let you explore different sides of the same world:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Political:&lt;/strong&gt; countries, borders, towns, rivers, and roads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cultural, Religions, and Provinces:&lt;/strong&gt; the people of the world, their beliefs, and its internal divisions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Biomes, Physical, and Heightmap:&lt;/strong&gt; the landscape, climate zones, ice, and elevation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Temperature, Precipitation, and Population:&lt;/strong&gt; climate and patterns of habitation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Goods, Trade, and Military:&lt;/strong&gt; resources, markets, trade routes, and armies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Emblems and Points of interest:&lt;/strong&gt; heraldry and places worth a journey.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also switch individual overlays on and off to create your own combination. Changing what is shown does not regenerate the world, so you can move between a political map and a climate map while keeping the same underlying geography.&lt;/p&gt;

&lt;p&gt;For your own landmarks, &lt;strong&gt;named world locations&lt;/strong&gt; let you mark locations and retain their names and coordinates as you change projection. A useful way to start making the world yours is to name the first island, mountain, or city that catches your imagination.&lt;/p&gt;

&lt;h2&gt;
  
  
  A World with Many Styles
&lt;/h2&gt;

&lt;p&gt;World layers come with &lt;strong&gt;fifteen presentation styles&lt;/strong&gt;: Atlas, Parchment, Ink, Climate, Ancient, Political Atlas, Clean, Cyberpunk, Dark Seas, Gloom, Light, Monochrome, Night, Pale, and Watercolor.&lt;/p&gt;

&lt;p&gt;Each changes the colours, lettering, and linework while keeping the geography in place. The same world can become a restrained reference atlas, an old parchment chart, a dark ocean map, or a softer illustrated view. You can explore the styles without committing to a new generation of the world.&lt;/p&gt;

&lt;p&gt;When it is time to share your work, export the terrain and visible world layers together as an image, or take the world layers out separately as &lt;strong&gt;vector SVG&lt;/strong&gt; or &lt;strong&gt;transparent PNG&lt;/strong&gt;. Vector exports include shapes and outlined lettering, with optional legend and scale, for further composition in a drawing application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Save and Share Your Worlds
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;&lt;code&gt;.fpp2&lt;/code&gt; document format&lt;/strong&gt; keeps terrain layers, physical elevations, sea level, seeds and generation settings, named places, and the generated atlas with its presentation. It is the format to use when you want to continue developing a 2.0 world.&lt;/p&gt;

&lt;p&gt;Your existing &lt;strong&gt;&lt;code&gt;.planet&lt;/code&gt; files still open&lt;/strong&gt;. Legacy export is also available for older workflows, including the bridge to World to Beyond. That export flattens the visible terrain into the original nine height categories, so keep a &lt;code&gt;.fpp2&lt;/code&gt; master to preserve the new layers, places, and atlas.&lt;/p&gt;

&lt;p&gt;Other import and export options include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Heightmap import:&lt;/strong&gt; greyscale images, colour-to-height mapping, and Terrain-RGB elevation data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;16-bit heightmaps:&lt;/strong&gt; take continuous terrain elevations into other terrain tools and game workflows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Painted map images and text:&lt;/strong&gt; exchange the underlying grid as an image or in the classic text format.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rendered PNGs:&lt;/strong&gt; export Physical and Legacy views up to &lt;strong&gt;8K on the long edge&lt;/strong&gt;, and Flat Land views up to &lt;strong&gt;4K&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drag-and-drop opening:&lt;/strong&gt; bring an existing world into the painting workspace.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rebuilt interface also brings a more adjustable workspace, scrollable palettes, canvas navigation, and built-in help for the main workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Flat Worlds and Familiar Favourites
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Physical Planet&lt;/strong&gt; is the new focus moving forward, but the other ways of making maps remain part of the app.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Legacy&lt;/strong&gt; preserves the original generator and its nine height categories, eleven projections, and eighteen themes. It is there for returning projects and for anyone who enjoys the original approach.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flat Land&lt;/strong&gt; is a useful choice for bounded maps rather than whole planets. It offers 2D and relief views, eight themes, and controls for erosion, hydrology, inland lakes, and the water plane. It can take your work in a different direction when you are thinking about a single landscape or a game map.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Experimental Steam Achievements
&lt;/h2&gt;

&lt;p&gt;We are also adding &lt;strong&gt;experimental Steam achievements&lt;/strong&gt; to bring a little extra fun to using the app. They celebrate creative milestones and encourage you to try different tools, views, painting techniques, and worldbuilding possibilities.&lt;/p&gt;

&lt;p&gt;Alongside them is a curated set of achievement illustrations with a stylized look. We hope these small rewards adds a bit more reward to your journey as you explore and create. They are also useful to guide you what to try next.&lt;/p&gt;

&lt;p&gt;Achievement support is experimental in this release, and we welcome feedback if an unlock does not behave as expected. Some milestones are tied to capabilities beyond the current public build, so the achievement list will continue to develop with the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  World to Beyond: A Rebuilt Map Studio
&lt;/h2&gt;

&lt;p&gt;The free &lt;a href="https://store.steampowered.com/app/4444360/Fantasy_Planet_Painter__World_to_Beyond/" rel="noopener noreferrer"&gt;World to Beyond DLC&lt;/a&gt; now arrives alongside 2.0 with a rebuilt GUI for creating maps for &lt;strong&gt;Beyond All Reason and other Spring/Recoil games&lt;/strong&gt;. The major road block from previous teaser is the now fully integrated map format generator, getting rid of third-party dependencies.&lt;/p&gt;

&lt;p&gt;The earlier exporter centred on turning a planet into a map. The new studio workflow gives the other parts of that map their own space: resources, vegetation, water, scenery, and the final output.&lt;/p&gt;

&lt;p&gt;Its seven stages cover the complete project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Source:&lt;/strong&gt; begin with a painted planet or a procedural map, choose a generation scheme, and set the map size.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terrain:&lt;/strong&gt; tune height range; procedural sources offer terrain styles, themes, generation controls, erosion, previews, and playability readouts. Imported heightmaps and ground textures provide further ways to bring in your own material.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metal:&lt;/strong&gt; place resources in a dedicated painting workspace, with automatic placement and brush controls including softness and symmetry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grass:&lt;/strong&gt; grow vegetation from terrain rules, then paint over the result to refine its coverage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Water:&lt;/strong&gt; adjust the water appearance and judge it against a live coastline sample.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Features:&lt;/strong&gt; use generated scenery or import &lt;code&gt;.obj&lt;/code&gt;, &lt;code&gt;.gltf&lt;/code&gt;, and &lt;code&gt;.glb&lt;/code&gt; models. Set scatter rules and paint a separate placement mask for each model—for example, trees along a ridge and boulders near the shore—and preview where they will land.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output:&lt;/strong&gt; build a complete &lt;code&gt;.sd7&lt;/code&gt; map archive, including terrain, textures, resources, grass, water settings, and scenery, with progress feedback and cancellation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is also a &lt;strong&gt;real-time 3D viewer&lt;/strong&gt; for inspecting the terrain, water, vegetation, scenery, resource spots, and start positions before compiling the archive. &lt;strong&gt;It currently requires OpenGL 4.3 support and will not work on macOS&lt;/strong&gt;. And if you are patient enough to read till this line - &lt;em&gt;there are more interactive ways to inspect and play with your map coming in future updates!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Studio projects can be saved as &lt;strong&gt;&lt;code&gt;.w2b&lt;/code&gt; files&lt;/strong&gt;, retaining settings and painted channels so you can return to a map over several sessions. Custom model files remain external references, so keep those assets with your project.&lt;/p&gt;

&lt;p&gt;Whether you want to turn an unusual planet into a battlefield or just use W2B as a really easy utility to assembly a fully functional BAR map, there is a lot of freedom here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Availability
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Free for existing owners:&lt;/strong&gt; 2.0 is an update to Fantasy Planet Painter, with the free World to Beyond DLC releasing alongside it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Windows first:&lt;/strong&gt; macOS is coming later; we will share more when it is ready.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Updated pricing:&lt;/strong&gt; the price for new purchases has already changed to reflect the app's progress. Regional prices are shown on the Steam store.&lt;/li&gt;
&lt;li&gt;The current build is released with the hope that you could try the new features early on - it may contain many features that we have not tested thoroughly - expect follow-up updates to address rough corners.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://store.steampowered.com/news/app/3336490/view/708909893000627309" rel="noopener noreferrer"&gt;Steam Annoucement&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>fantasyplanetpainter</category>
      <category>divooka</category>
      <category>methodox</category>
      <category>worldbuilding</category>
    </item>
    <item>
      <title>DevLog 20261007: A Detective Story - First Satisfying Fully Playable Build</title>
      <dc:creator>Charles Zhang</dc:creator>
      <pubDate>Wed, 07 Oct 2026 17:52:44 +0000</pubDate>
      <link>https://dev.to/methodox/devlog-20261007-a-detective-story-first-satisfying-full-playable-build-5d28</link>
      <guid>https://dev.to/methodox/devlog-20261007-a-detective-story-first-satisfying-full-playable-build-5d28</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;I've always wanted to develop a video game — in fact, many of them. &lt;em&gt;A Detective Story&lt;/em&gt; is far from the first attempt at Methodox; several earlier projects stalled along the way (you can still catch a glimpse of one in &lt;a href="https://methodox.itch.io/wayhome" rel="noopener noreferrer"&gt;Codename Wayhome&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;As we set out to build Divooka — a general-purpose visual programming system — the ability to create even a simple 2D game became an essential touchstone. So &lt;a href="https://store.steampowered.com/app/4484570/A_Detective_Story/" rel="noopener noreferrer"&gt;the game&lt;/a&gt; was never &lt;em&gt;just&lt;/em&gt; a game.&lt;/p&gt;

&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;The idea for &lt;em&gt;A Detective Story&lt;/em&gt; grew out of our work on Methodox ChatGateway, a RAG-based knowledge engine we were running as an online service. Behind the scenes we were also spending a lot of time with locally hosted LLMs and general knowledge-base applications. The underlying technology isn't new, but none of the existing solutions quite met my requirements. Most are poorly organized and extremely heavy in (Python) dependencies. I could talk about my complicated relationship with Python for hours — the love and, mostly, the hate — but one principle I value above almost everything else is portability and compactness in local software.&lt;/p&gt;

&lt;p&gt;A complete RAG knowledge-base solution shouldn't be much larger than the knowledge source itself. That's the bottom line.&lt;/p&gt;

&lt;p&gt;As with so many other things we build for Divooka, the best way to prove what the platform can deliver is to find a real application for ourselves and push it as far as we can. That led us to the idea of &lt;strong&gt;Crime Engine&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  On a Reusable, Configuration-Based Detective Story Game Engine
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Crime Engine&lt;/strong&gt; is a 2D detective-story game engine with fully configurable maps and characters. Players interrogate suspects using natural language, powered by locally hosted models through &lt;code&gt;llama.cpp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The setup is deliberately lightweight. It supports the major flavours of GGUF-based SLMs and includes built-in RAG. On the gameplay side we have map navigation, fully functional menus, story backgrounds, interrogation rooms, and a verdict screen for concluding a case. There are also interactions with NPCs on the streets in map view, item discovery, and item exchange.&lt;/p&gt;

&lt;p&gt;The item-exchange system turned out to be the trickiest piece. It may simply be a limitation of current local models, but I still don't find locally hosted LLMs reliable enough for consistent tool use.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Development of &lt;em&gt;A Detective Story&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;We announced the Steam “Coming Soon” page with a trailer back on &lt;a href="https://youtu.be/ZFBeZj6LN30?si=O0WVmJapNg_wIGXN" rel="noopener noreferrer"&gt;6 March&lt;/a&gt;, yet we never felt confident enough to release. Three main challenges kept holding us back:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Graphics quality&lt;/strong&gt; — Much of the art was AI-generated with manual adjustments, but the overall integration still fell short of the standard we wanted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NPC behaviour and tool use&lt;/strong&gt; — The item-exchange mechanic is genuinely interesting, but relying on an LLM for reliable outcomes proved difficult. We eventually solved this by scouting a stronger SLM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plot completeness&lt;/strong&gt; — Closely related to the point above: because LLM responses are hard to control tightly, it was tricky to guarantee that players could smoothly finish the game with all the necessary clues. Of course, Divooka remained our primary focus, so we couldn't pour unlimited effort into fine-tuning the plots.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Plot Work
&lt;/h2&gt;

&lt;p&gt;This is still an experimental game and a tech demo for the engine, yet we wanted it to feel like a coherent little story we could build on later — expansions, a series, that sort of thing. As we kept working, the characters became more polished: Ronan, Jack, Lady Garba, Helen, and the rest. It turns out it's simply fun to dwell in fiction once in a while.&lt;/p&gt;

&lt;p&gt;Even though it's a small 2D title, we ended up planning a surprisingly large set of graphics assets and subplots worth exploring. We originally sketched a gigantic cast of almost 500 NPCs, then deliberately shifted toward fewer, more personalized characters instead of a generic population.&lt;/p&gt;

&lt;h2&gt;
  
  
  Release
&lt;/h2&gt;

&lt;p&gt;Major improvements since the earlier builds include:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Significant engine work — better NPC stability, improved tool use, and far more thorough internal (automated) testing.&lt;/li&gt;
&lt;li&gt;Noticeable graphics upgrades.&lt;/li&gt;
&lt;li&gt;A full GUI overhaul using our proprietary framework &lt;code&gt;ShadowGUI&lt;/code&gt;. All Divooka users will benefit from this soon enough.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I did a final playtest today and, for the first time, the game felt genuinely enjoyable, smooth, and fun. It's almost ready to go.&lt;/p&gt;

&lt;p&gt;We plan to release &lt;em&gt;A Detective Story&lt;/em&gt; first while we continue polishing Crime Engine itself. It will be a while before CE is generally available.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://store.steampowered.com/news/app/4484570/view/693147294304832085" rel="noopener noreferrer"&gt;2026-10-03 Graphics Update Announcement&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>adetectivestory</category>
      <category>methodox</category>
      <category>divooka</category>
      <category>crimeengine</category>
    </item>
    <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>
  </channel>
</rss>
