<?xml version="1.0"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Mikołak</title>
    <link>https://xn--mikoak-6db.net</link>
    <atom:link href="https://xn--mikoak-6db.net/feed.xml" rel="self" type="application/rss+xml" />
    <description>mikołak.net blog</description>
    <language>en-us</language>
    <pubDate>Fri, 31 Oct 2025 00:15:00 +0100</pubDate>
    <lastBuildDate>Fri, 31 Oct 2025 00:15:00 +0100</lastBuildDate>

    <item>
      <title>Making LLM coding assistants create graphics&amp;#8230;&amp;#8203; and physical objects</title>
      <link>https://xn--mikoak-6db.net/blog/2025/coding-llms-making-graphics-and-physical-things.html</link>
      <pubDate>Wed, 29 Oct 2025 00:00:00 +0100</pubDate>
      <guid isPermaLink="false">blog/2025/coding-llms-making-graphics-and-physical-things.html</guid>
      	<description>
	&lt;div id=&quot;toc&quot; class=&quot;toc&quot;&gt;
&lt;div id=&quot;toctitle&quot;&gt;Table of Contents&lt;/div&gt;
&lt;ul class=&quot;sectlevel1&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#intro&quot;&gt;Intro&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#warmup_making_infographics&quot;&gt;Warmup: Making infographics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#main_course_prompts_to_objects&quot;&gt;Main course: Prompts to objects&lt;/a&gt;
&lt;ul class=&quot;sectlevel2&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#intro_2&quot;&gt;Intro&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#smoke_tests&quot;&gt;Smoke tests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#practical_prints&quot;&gt;Practical prints&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#outro&quot;&gt;Outro&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//llm-reality-coding/img/illustration-banner.png&quot; alt=&quot;illustration banner&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;div class=&quot;title&quot;&gt;Illustration: own work&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_1&quot; class=&quot;footnote&quot; href=&quot;#_footnote_1&quot; title=&quot;View footnote.&quot;&gt;1&lt;/a&gt;]&lt;/sup&gt; + background based on work of &lt;a href=&quot;https://unsplash.com/photos/a-computer-monitor-sitting-on-top-of-a-desk-76GU53nkLSU&quot; target=&quot;_blank&quot;&gt;Ezi on Unsplash&lt;/a&gt;.&lt;/div&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;intro&quot;&gt;Intro&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;I&amp;#8217;m sure most professional programmers by now have at least a passing familiarity with LLM-based coding assistants, whether through simple chat interfaces, or by trying not to get their prod data deleted by agentic approaches.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;They&amp;#8217;re not just a gimmick — they provide help with coding, &lt;a href=&quot;https://www.theregister.com/2025/07/11/ai_code_tools_slow_down/&quot; target=&quot;_blank&quot;&gt;apparent help with coding&lt;/a&gt;, and &lt;a href=&quot;https://www.theregister.com/2025/07/21/replit_saastr_vibe_coding_incident/&quot; target=&quot;_blank&quot;&gt;unambiguously deleterious &quot;help&quot; with coding&lt;/a&gt;. Key takeaway 💡: LLMs on occasion reduce cognitive load in 🧠 programming  and &lt;em&gt;OK, OK: I&amp;#8217;ll stop with the oversaturated facsimile of stereotypical LLM writing patterns&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Regardless of what one might think about the current &quot;AI&quot; situation, coding agents, or even code-focused LLM chat assistants do help with the drudgery of programming on occasion.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Here, however, we&amp;#8217;ll use both local and cloud-based LLMs to generate various outputs not traditionally thought of as &quot;code&quot;: some to increase productivity or provide more options at work, and some to perhaps expand one&amp;#8217;s overall toolset.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Before we proceed, one thing must absolutely be noted: for the topics covered, &lt;strong&gt;specialized models absolutely do already exist&lt;/strong&gt;. The point here is not to show what can be done with modern models, but how already-familiar tools can be used in more ways, without any specialized knowledge, or additional deployment requirements.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;warmup_making_infographics&quot;&gt;Warmup: Making infographics&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We&amp;#8217;ll start with something more universally usable. On occasion, when communicating more complex messages to your coworkers/team, the text grows so rich in content, it becomes easy to lose the general view over the particulars. Which is where a graphical aid would come in handy – such as an infographic. But not everyone has the skills, patience, and/or time to create them, even when taking account the availability of various web tools meant for that purpose.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;While it might seem counter-intuitive, generating graphics through a text-to-text coding-focused model is quite easy: just the matter of picking the right format; in our case, Scalable Vector Graphics (SVG).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;SVGs are pretty much the most common files for vector graphics – primarily used for diagrams and illustrations – on the Web. The crucial bit about SVGs, however, is that internally, they&amp;#8217;re just a completely human-readable XML file.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Here, let&amp;#8217;s create a simple infographic for working with an LLM with the following prompt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Create an SVG file representing a stylized infographic conveying the following information:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;One of the primary uses of modern LLMs is coding assistance,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;coding assistance supports both:&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;what&amp;#8217;s traditionally seen as &quot;coding languages&quot;, such as JavaScript, Java, Python, C/C++ and so on,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;but also anything that can be expressed in a code like format: graphics, music, even models of physical objects.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;while specialized models are usually better with all of that, being aware of the additional utility of general coding models gives you an advantage without the additional learning curve.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Keep the style clear, but in a typical infographic scheme. Enrich the infographic with small icons illustrating the most pertinent points.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Here&amp;#8217;s one result, from Claude Sonnet 4.5 (through Copilot):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//llm-reality-coding/img/infographics-sole-claude45.svg&quot; alt=&quot;infographics sole claude45&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This was obtained from just a single prompt, no followups, and looks relatively good!&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;If you&amp;#8217;re curious about the code itself, download &lt;a href=&quot;img/infographics-sole-claude45.svg&quot; target=&quot;_blank&quot;&gt;the SVG file&lt;/a&gt; and open it in a code editor.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//llm-reality-coding/img/infographics-code-snippet-claude45.png&quot; alt=&quot;infographics code snippet claude45&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 1. A snippet of the resultant SVG file.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Local models don&amp;#8217;t fare that well, comparatively, but still produce something approximating the desired result – here&amp;#8217;s an example from GLM 4.5 Air after a couple of corrections:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//llm-reality-coding/img/infographic-sole-glm4.5-air-reprompt.svg&quot; alt=&quot;infographic sole glm4.5 air reprompt&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;While this has some formatting issues, it at least provides a valid approximation of what we&amp;#8217;d want. And the great thing about generating these graphics in format such as SVG, as opposed to your typical genAI model outputting raster images, is that you can &lt;strong&gt;easily edit the file and correct any problems&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;To wit, this is the same file after ~3 minutes of editing in Inkscape:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//llm-reality-coding/img/infographic-sole-glm4.5-air-reprompt-edited.svg&quot; alt=&quot;infographic sole glm4.5 air reprompt edited&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 2. Some icons are still mispainted, but at least the layout is significantly improved.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In short, generating SVGs with LLMs conveys a similar advantage as with programming language code: it provides a decent &quot;jump off&quot; point, which then can be changed, refined, and generally made to one&amp;#8217;s liking; or just help with writer&amp;#8217;s block, especially when not having significant experience with graphic design.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;main_course_prompts_to_objects&quot;&gt;Main course: Prompts to objects&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;intro_2&quot;&gt;Intro&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Now, for our main course: we&amp;#8217;ll take the same chat assistants we use for coding, and make them help us create actual physical objects, just from a short concept spec.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Well, OK, for that to happen, we &lt;strong&gt;also&lt;/strong&gt; require a 3D printer. For those unfamiliar with 3D printing: an extremely simplified procedure to go from design to physical object is as follows:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;Design the object: typically in a CAD (Computer Aided Design) program, or via an equivalent coding framework (like OpenSCAD); alternatively, use a 3D editor like Blender.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Export the design to a format expressing surface geometry of the object(s), usually &lt;a href=&quot;https://en.wikipedia.org/wiki/STL_(file_format)&quot; target=&quot;_blank&quot;&gt;STLs&lt;/a&gt; nowadays.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open the STL file in a &lt;em&gt;slicer&lt;/em&gt;: a program that converts the objects surface geometry into an instruction sequence for the specific 3D printer setup (almost always split into layers, hence the name &quot;slicer&quot;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Send the instructions to the printer (here, we&amp;#8217;re using an &lt;a href=&quot;https://en.wikipedia.org/wiki/Fused_filament_fabrication&quot; target=&quot;_blank&quot;&gt;FFM&lt;/a&gt; printer), &lt;strong&gt;monitor the print, especially the first layer&lt;/strong&gt;, wait for the print to finish, cool down (optional), remove the print, and clean it up as needed.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;That first point reveals what we start with: we&amp;#8217;re going to prompt the LLMs to create OpenSCAD specifications. Unlike most CAD programs, OpenSCAD is really just a language for specifying 3D models, plus a runtime with relevant functionality (rendering, exports, etc.). While some CAD suites do use human-readable formats, OpenSCAD is the most prominent one actually intended to be directly written and edited by humans.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The plan is as follows: we&amp;#8217;ll start with using LLMs to attempt to describe some simple 3D solids, and, depending on the results, proceed to actually practical 3D prints.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;smoke_tests&quot;&gt;Smoke tests&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Let&amp;#8217;s try a basic scenario to check if the LLMs do encode the ability to actually generate OpenSCAD specifications.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Here&amp;#8217;s our starting prompt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Create an OpenSCAD file that encodes the following:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;a cube with a size of 10mm,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;a regular triangle with a side of &lt;code&gt;sqrt(2)&lt;/code&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;a half-sphere of radius 5mm.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We&amp;#8217;ll start with cloud models, invoked via Copilot. Using ChatGPT-4.1, we obtain:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Here is an OpenSCAD file that creates the requested shapes. Each shape is positioned so they do not overlap.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;pygments highlight&quot;&gt;&lt;code data-lang=&quot;openscad&quot;&gt;// Cube with size 10mm
translate([0, 0, 0])
cube(10);

// Regular triangle with side sqrt(2)
module regular_triangle(side) {
    h = side * sqrt(3) / 2;
    polygon(points=[
        [0, 0],
        [side, 0],
        [side/2, h]
    ]);
}
translate([15, 0, 0])
regular_triangle(sqrt(2));

// Half-sphere of radius 5mm
translate([30, 5, 0])
sphere(r=5, $fn=64);
translate([30, 5, 0])
cube([10, 10, 5], center=true);
difference() {
    translate([30, 5, 0])
    sphere(r=5, $fn=64);
    translate([30, 5, -5])
    cube([10, 10, 10], center=false);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The cube is at the origin.
The triangle is placed at [15, 0, 0] to avoid overlap.
The half-sphere is created using difference() to cut a sphere in half, placed at [30, 5, 0].&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Aaand here&amp;#8217;s how the result looks like:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//llm-reality-coding/img/openscad-smoke-1.png&quot; alt=&quot;openscad smoke 1&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Well, we actually got working code, and that&amp;#8217;s good&amp;#8230;&amp;#8203; but only the cube looks half-way close to what we intended. To be fair,
the triangle is on us, as we specified the parameters for a 3D object as if it was 2D.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Let&amp;#8217;s give ChatGPT another go, with the following correction:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The triangle is considerably too small, and the half-sphere renders as a full sphere within a cube. Please fix.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;admonitionblock note&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td class=&quot;icon&quot;&gt;
&lt;i class=&quot;fa icon-note&quot; title=&quot;Note&quot;&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class=&quot;content&quot;&gt;
The &quot;full sphere with a block&quot; description actually refers to the presentation &lt;strong&gt;before&lt;/strong&gt; a &quot;cut&quot; operation.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We obtain the following, supposedly corrected code:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Here&amp;#8217;s a corrected OpenSCAD file:
The triangle now has a more visible size (side length increased to 10mm).
The half-sphere is properly rendered by subtracting the lower half of the sphere.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;pygments highlight&quot;&gt;&lt;code&gt;// Cube with size 10mm
translate([0, 0, 0])
cube(10);

// Regular triangle with side 10mm
module regular_triangle(side) {
    h = side * sqrt(3) / 2;
    polygon(points=[
        [0, 0],
        [side, 0],
        [side/2, h]
    ]);
}
translate([15, 0, 0])
regular_triangle(10);

// Half-sphere of radius 5mm
difference() {
    translate([30, 5, 5])
    sphere(r=5, $fn=64);
    // Cut off the bottom half
    translate([25, 0, 5])
    cube([10, 10, 5], center=false);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The triangle is now clearly visible.
The half-sphere is created by subtracting a cube from the bottom half of the sphere.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;resulting in:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//llm-reality-coding/img/openscad-smoke-2.png&quot; alt=&quot;openscad smoke 2&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Much better, although, with all the hype towards the big-player LLMs, I would have expected for the half-sphere to be at least
oriented with the flat face downwards.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Anyway, after asking once more:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Have the triangle extrude upwards with a face height of 10mm.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We can see the following:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//llm-reality-coding/img/openscad-smoke-3.png&quot; alt=&quot;openscad smoke 3&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Let&amp;#8217;s try to form a refined prompt that will get us to our target in one go:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Create an OpenSCAD file that encodes the following:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;a cube with a size of &lt;code&gt;10mm&lt;/code&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;an extruded solid with a base consisting of a regular triangle with a side of &lt;code&gt;sqrt(2)*10mm&lt;/code&gt;, and an extrusion height of &lt;code&gt;10mm&lt;/code&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;a half-sphere of radius &lt;code&gt;5mm&lt;/code&gt;, with the flat side facing downwards, and located at the origin of the Z axis.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The response is:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Here is an OpenSCAD file that creates the requested shapes, positioned to avoid overlap:
Explanation:
The cube is placed at the origin.
The extruded triangle uses linear_extrude for a 3D solid, with the triangle base sized as specified.
The half-sphere is created by subtracting the lower half of a sphere, with the flat face on the XY plane at Z=0.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;pygments highlight&quot;&gt;&lt;code&gt;// Cube with size 10mm
translate([0, 0, 0])
cube(10);

// Extruded regular triangle (side = sqrt(2)*10mm, height = 10mm)
module regular_triangle(side) {
    h = side * sqrt(3) / 2;
    polygon(points=[
        [0, 0],
        [side, 0],
        [side/2, h]
    ]);
}
translate([20, 0, 0])
linear_extrude(height=10)
regular_triangle(sqrt(2)*10);

// Half-sphere of radius 5mm, flat side down at Z=0
difference() {
    translate([40, 5, 5])
    sphere(r=5, $fn=64);
    // Remove lower half
    translate([35, 0, 0])
    cube([10, 10, 5], center=false);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This will generate the three shapes as described, each separated along the X axis.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;And the code renders to the following:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//llm-reality-coding/img/openscad-smoke-final-prompt.png&quot; alt=&quot;openscad smoke final prompt&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;That looks about like something we wanted.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;baselines&quot;&gt;Baselines&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Having established a baseline prompt, let&amp;#8217;s run some tests comparing different LLMs. The selection of models stems
from both what is available through Copilot, and what is currently thought of as &quot;good&quot; local LLMs tailored for code generation.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The latter was produced through an extremely rigorous and formal approach of an ensemble method composed of the result of a deep research query based on the currently expressed trends in the prosumer space, validated by a cross-reference from the immediately available wetware compute&amp;amp;storage system instance.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In other words: I ran the query on last month&amp;#8217;s posts in &lt;a href=&quot;https://www.reddit.com/r/LocalLLaMA/&quot; target=&quot;_blank&quot;&gt;r/LocalLLaMA&lt;/a&gt; and checked it wasn&amp;#8217;t BS ,compared to my own impressions.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect4&quot;&gt;
&lt;h5 id=&quot;local_model_selection&quot;&gt;Local model selection&lt;/h5&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The models we&amp;#8217;re going to use here are all &lt;a href=&quot;https://docs.unsloth.ai/&quot;&gt;unsloth&amp;#8217;s&lt;/a&gt; quantizations/optimizations, specifically (&lt;code&gt;name: variant&lt;/code&gt;):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://huggingface.co/mistralai/Devstral-Small-2507&quot;&gt;Devstral-Small-2507&lt;/a&gt;: UD-Q8_K_XL&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://huggingface.co/unsloth/GLM-4.5-Air-GGUF&quot;&gt;GLM-4.5-Air&lt;/a&gt;: UD-IQ2_XXS&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://huggingface.co/unsloth/Qwen3-Coder-30B-A3B-Instruct-GGUF&quot;&gt;Qwen3-Coder-30B-A3B-Instruct&lt;/a&gt;: UD-Q8_K_XL&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://huggingface.co/unsloth/gpt-oss-20b-GGUF&quot;&gt;gpt-oss-20b&lt;/a&gt;: F16&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As far as configuration goes:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;everything runs through &lt;a href=&quot;https://github.com/ggml-org/llama.cpp&quot; target=&quot;_blank&quot;&gt;llama.cpp&lt;/a&gt; (with &lt;a href=&quot;https://github.com/mostlygeek/llama-swap&quot;&gt;llama-swap&lt;/a&gt; for convenience),&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;for every model, the context window size is 16k (mostly for a &quot;thinking buffer&quot;),&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;--jinja&lt;/code&gt; (unsloth&amp;#8217;s suggested pre-prompts) is enabled wherever recommended in the model card.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;A final clarification: the specific choice of quantization originates from the goal of all models fitting in roughly the same amount of VRAM (under 48 GB). This is theoretically not strictly &quot;fair&quot;&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_2&quot; class=&quot;footnote&quot; href=&quot;#_footnote_2&quot; title=&quot;View footnote.&quot;&gt;2&lt;/a&gt;]&lt;/sup&gt;, but, firstly, it matches actual usage scenarios better, and modern larger models often exhibit surprisingly good performance even with more aggressive quantizations anyway.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect4&quot;&gt;
&lt;h5 id=&quot;setup&quot;&gt;Setup&lt;/h5&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Now, back to our tests. For each model, we&amp;#8217;ll do best of three, and show just that.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;admonitionblock note&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td class=&quot;icon&quot;&gt;
&lt;i class=&quot;fa icon-note&quot; title=&quot;Note&quot;&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class=&quot;content&quot;&gt;
Some visualizations include solids that are translucent. This is a manual toggle for visualization purposes, i.e. not an artifact of the model output.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;table class=&quot;tableblock frame-all grid-all spread&quot;&gt;
&lt;colgroup&gt;
&lt;col style=&quot;width: 16.6666%;&quot;&gt;
&lt;col style=&quot;width: 16.6666%;&quot;&gt;
&lt;col style=&quot;width: 16.6666%;&quot;&gt;
&lt;col style=&quot;width: 16.6666%;&quot;&gt;
&lt;col style=&quot;width: 16.6666%;&quot;&gt;
&lt;col style=&quot;width: 16.667%;&quot;&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;Model&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;Cube&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;Triangle Extrude&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;Half-sphere&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;Visualization&lt;/th&gt;
&lt;th class=&quot;tableblock halign-left valign-top&quot;&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;GPT-4.1&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;../../illustrations//llm-reality-coding/img/openscad-base-gpt41.png&quot; alt=&quot;openscad base gpt41&quot; width=&quot;100&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;-&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;GPT-5 mini&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;../../illustrations//llm-reality-coding/img/openscad-base-gpt5mini.png&quot; alt=&quot;openscad base gpt5mini&quot; width=&quot;100&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Needed retries.&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Claude Sonnet 4.5&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;../../illustrations//llm-reality-coding/img/openscad-base-claude45.png&quot; alt=&quot;openscad base claude45&quot; width=&quot;100&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Insufficient spacing.&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Gemini 2.5 Pro&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;../../illustrations//llm-reality-coding/img/openscad-base-gemini25pro.png&quot; alt=&quot;openscad base gemini25pro&quot; width=&quot;100&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Better presentation, clean code, included smoothing.&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Gemini 2.0 Flash&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;❌&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;❌&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;../../illustrations//llm-reality-coding/img/openscad-base-gemini20flash.png&quot; alt=&quot;openscad base gemini20flash&quot; width=&quot;100&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Insufficient spacing.&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Qwen3-Coder-30B-A3B&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;❌&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;../../illustrations//llm-reality-coding/img/openscad-base-qwen3-coder.png&quot; alt=&quot;openscad base qwen3 coder&quot; width=&quot;100&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;The sphere &lt;strong&gt;is&lt;/strong&gt; cut, but at the wrong height. Insufficient spacing.&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;GLM-4.5-Air&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;../../illustrations//llm-reality-coding/img/openscad-base-glm-4.5-air.png&quot; alt=&quot;openscad base glm 4.5 air&quot; width=&quot;100&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Strong overlap.&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;Devstral Small 2507&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;❌&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;../../illustrations//llm-reality-coding/img/openscad-base-devstral-small-2507.png&quot; alt=&quot;openscad base devstral small 2507&quot; width=&quot;100&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;-&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;GPT-OSS 20B&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;✅&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;❌&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;&lt;span class=&quot;image&quot;&gt;&lt;img src=&quot;../../illustrations//llm-reality-coding/img/openscad-base-gptoss20b.png&quot; alt=&quot;openscad base gptoss20b&quot; width=&quot;100&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class=&quot;tableblock halign-left valign-top&quot;&gt;&lt;p class=&quot;tableblock&quot;&gt;The half-sphere cut is present, but incorrectly offset.&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Overall, most &quot;full&quot; cloud models managed to get everything right spec-wise, and included some sensible formatting. On the other hand, local models struggled with the half-sphere more often than not, with only GLM-4.5-Air managing to get it right.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;It would be unfair not to mention that our setup is relatively brutal towards local models. There&amp;#8217;s no fine-tuning, no prompt engineering beyond the base prompt, or other methods to wring more of them. Even a follow-up query to check &quot;their work&quot; for errors results in better output. Nevertheless, I&amp;#8217;d argue the comparisons actually demonstrate how &lt;strong&gt;good&lt;/strong&gt; current local models are.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Right, let&amp;#8217;s proceed to the next experiment&amp;#8230;&amp;#8203; just joking, of course we&amp;#8217;re going to print the results!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;from_matrix_multiplication_to_plastic_strings&quot;&gt;From matrix multiplication to plastic strings&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Let&amp;#8217;s take GLM 4.5-Air&amp;#8217;s output as an example. This is the code:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;pygments highlight&quot;&gt;&lt;code data-lang=&quot;openscad&quot;&gt;// Cube of size 10mm
cube(10);

// Extruded triangular prism with base sqrt(2)*10mm and height 10mm
s = sqrt(2) * 10;
h = (sqrt(3)/2) * s;
points = [
    [0, h * 2/3],
    [-s/2, -h/3],
    [s/2, -h/3]
];
linear_extrude(height=10) polygon(points);

// Half-sphere (hemisphere) of radius 5mm with flat face downward
difference() {
    sphere(r=5, $fn=20);
    translate([0,0,-5]) cube(10, center=true);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This is the initial rendering (in FreeCAD):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//llm-reality-coding/img/openscad-base-glm-4.5-air.png&quot; alt=&quot;openscad base glm 4.5 air&quot; width=&quot;300&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This is the print timelapse&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_3&quot; class=&quot;footnote&quot; href=&quot;#_footnote_3&quot; title=&quot;View footnote.&quot;&gt;3&lt;/a&gt;]&lt;/sup&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;videoblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;video src=&quot;../../illustrations//llm-reality-coding/img/smoke-print-timelapse.mp4&quot; width=&quot;900&quot; height=&quot;controls&quot; controls&gt;
Your browser does not support the video tag.
&lt;/video&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;And this is the result, printed at a &quot;rough&quot; 0.3mm layer height – usually reserved for functional parts – in white PLA:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//llm-reality-coding/img/smoke-print-photo.jpeg&quot; alt=&quot;smoke print photo&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 3. &lt;em&gt;Something something late 90s video game publisher slogan&lt;/em&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Kinda&apos; cool, right?&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;practical_prints&quot;&gt;Practical prints&lt;/h3&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;design&quot;&gt;Design&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We&amp;#8217;ve gone through a simple scenario for initial validation. Now, onto something more practical.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;3D printing, specifically &lt;a href=&quot;https://en.wikipedia.org/wiki/Fused_filament_fabrication&quot;&gt;FDM/FFF printing&lt;/a&gt;, offers some unique opportunities not present in other manufacturing methods. One of these is the ability to create intrinsically enclosed and/or enmeshed multimaterial assemblies. During the print process, we can pause at specific layers, and add/mount subcomponents providing additional capabilities.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;And you know what&amp;#8217;s annoying? When you need to assemble something – like a piece of furniture – you have your set of screws, bolts, or other small elements&amp;#8230;&amp;#8203; so you lay them out in a more or less organized fashion. But then, then your concentration gets broken, you need to get up to retrieve something, and you (or a less or more small furry critter you house, you didn&amp;#8217;t notice coming in) end up kicking said small things all over the place.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Which leads to the following project: a small tray with several compartments, and, critically, embedded magnets in the compartment floors – to hold the aforementioned screws, etc. down.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This time, we&amp;#8217;re going to try both cloud and local models, but now more informally, with a limited selection, and allowing for some corrections.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Keeping things more concise, what follows is the prompt that resulted in the best initial results:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Create an OpenSCAD file that encodes a 50 mm by 50 mm tray divided into four compartments. The divider and external walls of the tray must be 5mm tall and 1.2mm wide. The base of the tray should be at least 3 mm thick, and include voids able to accommodate 4mm x 10mm x 2 mm magnets. The voids should be placed at the center of each compartment, and be no farther away than 0.6mm from the top surface of the base of the tray.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Claude Sonnet (3.7 Thinking) produced an interesting case, i.e. two solids:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//llm-reality-coding/img/openscad-tray-claude37t.png&quot; alt=&quot;openscad tray claude37t&quot; width=&quot;300&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 4. &lt;a href=&quot;https://gist.github.com/mikolak-net/96f6fbfde61354d19bc9482e66a93349#file-openscad-tray-claude37t-scad&quot; target=&quot;_blank&quot;&gt;(gist)&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Gemini 2.5 Pro needed to correct a syntax error, but produced a single solid with proper magnet holes:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//llm-reality-coding/img/openscad-tray-gemini25pro.png&quot; alt=&quot;openscad tray gemini25pro&quot; width=&quot;300&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 5. &lt;a href=&quot;https://gist.github.com/mikolak-net/96f6fbfde61354d19bc9482e66a93349#file-openscad-tray-gemini25pro-scad&quot; target=&quot;_blank&quot;&gt;(gist)&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;GPT-4.1 generated a curious abberation, with some elements clearly inverted compared to what they should be:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//llm-reality-coding/img/openscad-tray-gpt41.png&quot; alt=&quot;openscad tray gpt41&quot; width=&quot;300&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 6. &lt;a href=&quot;https://gist.github.com/mikolak-net/96f6fbfde61354d19bc9482e66a93349#file-openscad-tray-gpt41-scad&quot; target=&quot;_blank&quot;&gt;(gist)&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;But after a couple of followups, it got there.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//llm-reality-coding/img/openscad-tray-gpt41-fixed.png&quot; alt=&quot;openscad tray gpt41 fixed&quot; width=&quot;300&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 7. &lt;a href=&quot;https://gist.github.com/mikolak-net/96f6fbfde61354d19bc9482e66a93349#file-openscad-tray-gpt41-fixed-scad&quot; target=&quot;_blank&quot;&gt;(gist)&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Now onto the local models. gpt-oss-20b actually got it right on the first try – outshining the cloud&amp;#8217;s Claude 3.5 !&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//llm-reality-coding/img/openscad-tray-gptoss20b.png&quot; alt=&quot;openscad tray gptoss20b&quot; width=&quot;300&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 8. &lt;a href=&quot;https://gist.github.com/mikolak-net/96f6fbfde61354d19bc9482e66a93349#file-openscad-tray-gptoss20b-scad&quot; target=&quot;_blank&quot;&gt;(gist)&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Qwen3-Coder-30B-A3B got off to a rocky start:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//llm-reality-coding/img/openscad-tray-qwen3-coder.png&quot; alt=&quot;openscad tray qwen3 coder&quot; width=&quot;300&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 9. &lt;a href=&quot;https://gist.github.com/mikolak-net/96f6fbfde61354d19bc9482e66a93349#file-openscad-tray-qwen3-coder-scad&quot; target=&quot;_blank&quot;&gt;(gist)&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;It finally turned around, after almost hitting the context window limit, needing to be explicitly told what one major issue is (incorrect assumption for the location of the coordinate origin), and needed to be kept reminded not to introduce &quot;magic numbers&quot;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//llm-reality-coding/img/openscad-tray-qwen3-coder-fixed.png&quot; alt=&quot;openscad tray qwen3 coder fixed&quot; width=&quot;300&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 10. &lt;a href=&quot;https://gist.github.com/mikolak-net/96f6fbfde61354d19bc9482e66a93349#file-openscad-tray-qwen3-coder-fixed-scad&quot; target=&quot;_blank&quot;&gt;(gist)&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Devstral got it so completely wrong that it&amp;#8217;s difficult to say where to start:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//llm-reality-coding/img/openscad-tray-devstral-small-2507.png&quot; alt=&quot;openscad tray devstral small 2507&quot; width=&quot;300&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 11. &lt;a href=&quot;https://gist.github.com/mikolak-net/96f6fbfde61354d19bc9482e66a93349#file-openscad-tray-devstral-small-2507-scad&quot; target=&quot;_blank&quot;&gt;(gist)&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;However, it only took two followups to get the correct solid. And unlike Qwen3-Coder, Devstral immediately acted on the feedback about the Z offset for the magnet pockets being incorrect.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;GLM 4.5 Air is a funny case. The first result was completely wrong:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//llm-reality-coding/img/openscad-tray-glm-4.5-air.png&quot; alt=&quot;openscad tray glm 4.5 air&quot; width=&quot;300&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 12. &lt;a href=&quot;https://gist.github.com/mikolak-net/96f6fbfde61354d19bc9482e66a93349#file-openscad-tray-glm-4-5-air-scad&quot; target=&quot;_blank&quot;&gt;(gist)&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;and it didn&amp;#8217;t manage to produce a sensible fix before running out of context window. It appeared to code in &lt;strong&gt;everything but the base&lt;/strong&gt; as a void, and got stuck on that.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&amp;#8230;&amp;#8203;but got it the model completely right on the very second try:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//llm-reality-coding/img/openscad-tray-glm-4.5-air-iter.png&quot; alt=&quot;openscad tray glm 4.5 air iter&quot; width=&quot;300&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 13. &lt;a href=&quot;https://gist.github.com/mikolak-net/96f6fbfde61354d19bc9482e66a93349#file-openscad-tray-glm-4-5-air-iter-scad&quot; target=&quot;_blank&quot;&gt;(gist)&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;implementation&quot;&gt;Implementation&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Theoretically, we&amp;#8217;re now ready to put the models in the slicer and print something out. Practically, there is a problem, which I&amp;#8217;m sure any reader with some experience in FFM manufacture has already noticed.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Let&amp;#8217;s cut to the chase: &lt;strong&gt;none&lt;/strong&gt; of the designs provide &lt;strong&gt;any&lt;/strong&gt; allowance for:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;the installed magnets having anything else than the provided dimensions,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the print itself actually not holding its desired dimensions.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The above is actually quite damning: handling tolerances is fundamental not only to 3D printing, but &lt;strong&gt;any&lt;/strong&gt; manufacturing process. Barring some Angstrom-level nanofabrication&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_4&quot; class=&quot;footnote&quot; href=&quot;#_footnote_4&quot; title=&quot;View footnote.&quot;&gt;4&lt;/a&gt;]&lt;/sup&gt;, nothing fits &lt;strong&gt;precisely&lt;/strong&gt;. And don&amp;#8217;t get me wrong: modern consumer 3D printers offer a crazy level of precision for their purchase price. Still, when 0 mm precision implies 1e-5 mm clearance, that&amp;#8217;s still that many moles of plastic or metal that need to fit &lt;strong&gt;somewhere&lt;/strong&gt;. Sure, most materials provide some give, but there&amp;#8217;s a limit.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The choice of magnets as our inclusion into the gem we&amp;#8217;re creating was quite intentional. Magnets introduce their own particular problems in 3D printing, for the virtue of being what they are. If the print bed attracts the magnets well enough (and many bed surfaces are actually magnetized), there&amp;#8217;s the risk of the magnet ripping off the bottom layers of the object as its being removed. And if the printer&amp;#8217;s toolhead&amp;#8217;s nozzle is attractive to the magnet enough&amp;#8230;&amp;#8203; congratulations, you&amp;#8217;ve earned an achievement of a relative exotic print failure, possibly with physical printer damage.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Back in the context of LLMs, all of the above highlights one of their major flaws: their lack of perceived &quot;understanding&quot; of various intricacies of problem domains – usually followed by some variation of &quot;Of course, I completely [&amp;#8230;&amp;#8203;]&quot; and assorted simulated self-flagellation (actual course correction not guaranteed).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The flipside of the above is that chat-specialized LLMs can and do correct themselves, even with vague prompts. And, for example, Claude&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_5&quot; class=&quot;footnote&quot; href=&quot;#_footnote_5&quot; title=&quot;View footnote.&quot;&gt;5&lt;/a&gt;]&lt;/sup&gt; did exactly that, provided a prompt to correct its original output:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Review the provided code for any possible tweaks that are required, or recommended, for a 3D print of the object to succeed. Apply those tweaks to the original code, while still preserving the validity of it with respect to the original spec.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;pygments highlight&quot;&gt;&lt;code data-lang=&quot;openscad&quot;&gt;// Tray with 4 compartments and magnet voids
// Dimensions: 50mm x 50mm

// Parameters
tray_width = 50;
tray_length = 50;
wall_height = 5;
wall_thickness = 1.2;
base_thickness = 3;

magnet_width = 4;
magnet_length = 10;
magnet_height = 2;
magnet_offset_from_top = 0.6;

// Print tolerances
magnet_tolerance = 0.15; // Added clearance for easier magnet insertion
chamfer_size = 0.3; // Chamfer at void opening

// Calculate internal dimensions
inner_width = tray_width - 2 * wall_thickness;
inner_length = tray_length - 2 * wall_thickness;
compartment_width = (inner_width - wall_thickness) / 2;
compartment_length = (inner_length - wall_thickness) / 2;

module magnet_void_with_chamfer() {
    // Main void with tolerance
    cube([magnet_width + magnet_tolerance, magnet_length + magnet_tolerance, magnet_height + 0.1]);

    // Chamfer at opening
    translate([0, 0, magnet_height])
    linear_extrude(height = chamfer_size, scale = [(magnet_width + magnet_tolerance + 2*chamfer_size)/(magnet_width + magnet_tolerance), (magnet_length + magnet_tolerance + 2*chamfer_size)/(magnet_length + magnet_tolerance)])
    translate([0, 0, 0])
    square([magnet_width + magnet_tolerance, magnet_length + magnet_tolerance]);
}

module base() {
    difference() {
        // Base plate
        cube([tray_width, tray_length, base_thickness]);

        // Magnet voids in each compartment
        // Top-left
        translate([
            wall_thickness + compartment_width/2 - (magnet_width + magnet_tolerance)/2,
            wall_thickness + compartment_length/2 - (magnet_length + magnet_tolerance)/2,
            base_thickness - magnet_height - magnet_offset_from_top
        ])
        magnet_void_with_chamfer();

        // Top-right
        translate([
            wall_thickness + compartment_width + wall_thickness + compartment_width/2 - (magnet_width + magnet_tolerance)/2,
            wall_thickness + compartment_length/2 - (magnet_length + magnet_tolerance)/2,
            base_thickness - magnet_height - magnet_offset_from_top
        ])
        magnet_void_with_chamfer();

        // Bottom-left
        translate([
            wall_thickness + compartment_width/2 - (magnet_width + magnet_tolerance)/2,
            wall_thickness + compartment_length + wall_thickness + compartment_length/2 - (magnet_length + magnet_tolerance)/2,
            base_thickness - magnet_height - magnet_offset_from_top
        ])
        magnet_void_with_chamfer();

        // Bottom-right
        translate([
            wall_thickness + compartment_width + wall_thickness + compartment_width/2 - (magnet_width + magnet_tolerance)/2,
            wall_thickness + compartment_length + wall_thickness + compartment_length/2 - (magnet_length + magnet_tolerance)/2,
            base_thickness - magnet_height - magnet_offset_from_top
        ])
        magnet_void_with_chamfer();
    }
}

module walls() {
    // Outer walls
    difference() {
        cube([tray_width, tray_length, wall_height + base_thickness]);
        translate([wall_thickness, wall_thickness, base_thickness])
        cube([inner_width, inner_length, wall_height + 1]);
    }

    // Horizontal divider
    translate([wall_thickness, tray_length/2 - wall_thickness/2, base_thickness])
    cube([inner_width, wall_thickness, wall_height]);

    // Vertical divider
    translate([tray_width/2 - wall_thickness/2, wall_thickness, base_thickness])
    cube([wall_thickness, inner_length, wall_height]);
}

// Render the complete tray
base();
walls();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Summarized as:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Key improvements:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Magnet voids now 0.15mm larger for press-fit tolerance&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Chamfered openings prevent filament strings and ease magnet insertion&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;All dimensions preserved per original spec&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Print-friendly geometry maintained&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;That &lt;strong&gt;is&lt;/strong&gt; an improvement, although one gripe remains in the tolerance size, usually dependent on the layer thickness.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;A surprising outcome is the addition of &lt;a href=&quot;https://en.wikipedia.org/wiki/Chamfer&quot; target=&quot;_blank&quot;&gt;chamfers&lt;/a&gt;: on this scale, will do sweet FA, but it shows the relevant embeddings for &lt;strong&gt;general&lt;/strong&gt; 3D printing improvements are present.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Let us revert to local models. For GLM 4.5 Air, the model did attempt to make the output better:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Key Improvements for 3D Printability:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Base Thickness Increase:&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Increased from 3mm to 4mm to provide more material below magnet voids&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ensures 1.4mm material below voids (vs. 0.4mm in original) for structural integrity&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Still meets &quot;at least 3mm&quot; specification requirement&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Magnet Void Positioning:&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Voids now start at z=1.4mm (previously z=0.4mm) in a 4mm base&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Maintains 0.6mm clearance from top surface&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Preserves exact 4x10x2mm magnet dimensions&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Structural Enhancements:&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Eliminated thin-walled sections below magnets that would be prone to breakage&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Maintained all original dimensional specifications&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Kept wall thickness at 1.2mm (suitable for FDM printing)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Print Orientation Recommendations:&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Print with base down (as modeled)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Supports should be added for internal divider bases if needed&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Print at 0.2mm layer height for detail retention&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Material Considerations:&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Total height now 9mm (4mm base + 5mm walls)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Maintains compartment size (25x25mm) and wall thickness (1.2mm)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Preserves exact 50x50mm external dimensions&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;You&amp;#8217;ll note the conspicuous lack of tolerance factors for the magnet voids.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Giving it more of a hint:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;These are good, but miss one aspect. Here&amp;#8217;s a hint: when inserting objects into a 3D print, are they generally the exact size specified, and are the voids in the print also of that exact size?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Using the hint, apply tweaks to the provided code, while still preserving the validity of it with respect to the original spec.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;provides us with:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;pygments highlight&quot;&gt;&lt;code data-lang=&quot;openscad&quot;&gt;// Tray dimensions
tray_width = 50;    // mm
tray_length = 50;   // mm
tray_height_base = 4;  // Increased from 3mm for strength
wall_thickness = 1.2;  // mm
wall_height = 5;       // mm
compartment_size = tray_width / 2;  // 25mm each

// Magnet dimensions with tolerance for insertion
magnet_width = 4;    // mm
magnet_length = 10;  // mm
magnet_height = 2;   // mm
magnet_tolerance = 0.2;  // Added tolerance per dimension
magnet_depth_offset = 0.6;  // mm from top of base

// Effective magnet void dimensions (with tolerance)
void_width = magnet_width + 2 * magnet_tolerance;
void_length = magnet_length + 2 * magnet_tolerance;
void_height = magnet_height + magnet_tolerance;  // Only add tolerance to depth

// Calculate positions for magnet voids
magnet_x = compartment_size / 2 - magnet_length / 2;
magnet_y = compartment_size / 2 - magnet_width / 2;
magnet_z = tray_height_base - magnet_depth_offset - magnet_height;

// Generate the tray
difference() {
    // Base and walls combined
    union() {
        // Base (now 4mm thick)
        cube([tray_width, tray_length, tray_height_base], center=false);

        // External walls
        translate([0, 0, tray_height_base])
            cube([wall_thickness, tray_length, wall_height]);          // Left wall
        translate([tray_width - wall_thickness, 0, tray_height_base])
            cube([wall_thickness, tray_length, wall_height]);          // Right wall
        translate([0, 0, tray_height_base])
            cube([tray_width, wall_thickness, wall_height]);          // Bottom wall
        translate([0, tray_length - wall_thickness, tray_height_base])
            cube([tray_width, wall_thickness, wall_height]);          // Top wall

        // Internal dividers
        translate([compartment_size, 0, tray_height_base])
            cube([wall_thickness, tray_length, wall_height]);          // Vertical divider
        translate([0, compartment_size, tray_height_base])
            cube([tray_width, wall_thickness, wall_height]);          // Horizontal divider
    }

    // Magnet voids in each compartment (with tolerance)
    // Bottom-left
    translate([magnet_x - magnet_tolerance, magnet_y - magnet_tolerance, magnet_z])
        cube([void_length, void_width, void_height]);

    // Bottom-right
    translate([magnet_x + compartment_size - magnet_tolerance, magnet_y - magnet_tolerance, magnet_z])
        cube([void_length, void_width, void_height]);

    // Top-left
    translate([magnet_x - magnet_tolerance, magnet_y + compartment_size - magnet_tolerance, magnet_z])
        cube([void_length, void_width, void_height]);

    // Top-right
    translate([magnet_x + compartment_size - magnet_tolerance, magnet_y + compartment_size - magnet_tolerance, magnet_z])
        cube([void_length, void_width, void_height]);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The model outputs a suboptimal result, in that it quasi-hallucinates the tolerance values w.r.t. height. Nevertheless, and unlike the cloud-based model, it potentially corrects for &lt;strong&gt;both&lt;/strong&gt; aspects of magnet inclusion in 3D prints.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;And here&amp;#8217;s gpt-oss&apos; result:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;pygments highlight&quot;&gt;&lt;code data-lang=&quot;openscad&quot;&gt;//============================================================
//  50 × 50 mm Tray with 4 compartments
//  Walls: 5 mm tall, 1.2 mm wide
//  Base: 3 mm thick
//  Magnet voids (4 × 10 × 2 mm) – centred in each compartment
//  Void top is 0.6 mm from the top of the base
//============================================================

//---------------------------
//  Parameters
//---------------------------
tray_size      = 50;      // overall tray size (mm)
wall_thickness = 1.2;     // wall thickness (mm) – thin for FDM, keep spec
wall_height    = 5;       // wall height (mm)
base_thickness = 3;       // base thickness (mm)

magnet_size      = [4, 10, 2];   // [x, y, z] size of the magnet void (mm)
magnet_depth_top = 0.6;          // distance from top of base to top of void (mm)
magnet_z_bottom  = base_thickness - magnet_depth_top - magnet_size[2]; // z of void bottom (mm)

//---------------------------
//  Helper modules
//---------------------------
/// Draws a single magnet void at the given centre point
module magnet_void(centre) {
    translate([ centre[0] - magnet_size[0]/2,
                centre[1] - magnet_size[1]/2,
                magnet_z_bottom ])
        cube( magnet_size );
}

/// Base of the tray – 50 × 50 × 3 mm – with the four voids subtracted
module base_with_voids() {
    difference() {
        color(&quot;lightgrey&quot;)  // base colour (preview only)
            cube([tray_size, tray_size, base_thickness]);

        // centres of the four compartments
        centres = [
            [12.8, 12.8],   // bottom‑left
            [37.2, 12.8],   // bottom‑right
            [12.8, 37.2],   // top‑left
            [37.2, 37.2]    // top‑right
        ];

        for (c = centres)
            magnet_void(c);          // subtract the void
    }
}

/// Outer walls – 5 mm tall, 1.2 mm thick, positioned on top of the base
module outer_walls() {
    difference() {
        translate([0, 0, base_thickness])  // walls start on top of base
            color(&quot;tan&quot;)  // wall colour (preview only)
                cube([tray_size, tray_size, wall_height]);

        translate([wall_thickness, wall_thickness, base_thickness])
            cube([ tray_size - 2*wall_thickness,
                   tray_size - 2*wall_thickness,
                   wall_height ]);
    }
}

/// Two internal divider walls (vertical + horizontal)
module divider_walls() {
    // vertical divider – centred at x = 25 mm
    translate([ 25 - wall_thickness/2,
                wall_thickness,
                base_thickness ])
        color(&quot;tan&quot;)
            cube([ wall_thickness,
                   tray_size - 2*wall_thickness,
                   wall_height ]);

    // horizontal divider – centred at y = 25 mm
    translate([ wall_thickness,
                25 - wall_thickness/2,
                base_thickness ])
        color(&quot;tan&quot;)
            cube([ tray_size - 2*wall_thickness,
                   wall_thickness,
                   wall_height ]);
}

//---------------------------
//  Assemble the tray
//---------------------------
union() {
    base_with_voids();
    outer_walls();
    divider_walls();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;While it did provide a significantly streamlined code (unprompted bells and whistles included), it also still needed an explicit reminder about the precision issue before it outputted a model with tolerances taken into account.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Right, let&amp;#8217;s get to the printing.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;results&quot;&gt;Results&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We&amp;#8217;ll take the GLM 4.5 Air model for the print, with the following changes:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;slightly adjusting:&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;the numerical values for the tolerance,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the offset from the top,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;and the thickness of the base;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;adding an additional z clearance to the magnet void height;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;and fixing the &lt;code&gt;void_height&lt;/code&gt; calculation, which should also be based on &lt;code&gt;magnet_depth_offset&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The base thickness and top offset changes, or rather, the need for them, became apparent when it turned out that the currently-used nozzle is &lt;strong&gt;significantly&lt;/strong&gt; more magnetic than expected – so the magnets need to be nested deeper into the print, which will make them hopefully attracted more to the printbed than to the nozzle.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;pygments highlight&quot;&gt;&lt;code&gt;// Tray dimensions
tray_width = 50;    // mm
tray_length = 50;   // mm
tray_height_base = 6;  // Increased from 3mm for strength
wall_thickness = 1.2;  // mm
wall_height = 5;       // mm
compartment_size = tray_width / 2;  // 25mm each

// Magnet dimensions with tolerance for insertion
magnet_width = 4;    // mm
magnet_length = 10;  // mm
magnet_height = 2;   // mm
magnet_tolerance = 0.6;  // Added tolerance per dimension
magnet_z_clearance = 2.1;  // Additional clearance in height
magnet_depth_offset = 0.6;  // mm from top of base

// Effective magnet void dimensions (with tolerance)
void_width = magnet_width + 2 * magnet_tolerance;
void_length = magnet_length + 2 * magnet_tolerance;
void_height = magnet_height + magnet_tolerance + magnet_z_clearance;  // Only add tolerance to depth

// Calculate positions for magnet voids
magnet_x = compartment_size / 2 - magnet_length / 2;
magnet_y = compartment_size / 2 - magnet_width / 2;
magnet_z = tray_height_base - void_height - magnet_depth_offset;

// Generate the tray
difference() {
    // Base and walls combined
    union() {
        // Base (now 4mm thick)
        cube([tray_width, tray_length, tray_height_base], center=false);

        // External walls
        translate([0, 0, tray_height_base])
            cube([wall_thickness, tray_length, wall_height]);          // Left wall
        translate([tray_width - wall_thickness, 0, tray_height_base])
            cube([wall_thickness, tray_length, wall_height]);          // Right wall
        translate([0, 0, tray_height_base])
            cube([tray_width, wall_thickness, wall_height]);          // Bottom wall
        translate([0, tray_length - wall_thickness, tray_height_base])
            cube([tray_width, wall_thickness, wall_height]);          // Top wall

        // Internal dividers
        translate([compartment_size, 0, tray_height_base])
            cube([wall_thickness, tray_length, wall_height]);          // Vertical divider
        translate([0, compartment_size, tray_height_base])
            cube([tray_width, wall_thickness, wall_height]);          // Horizontal divider
    }

    // Magnet voids in each compartment (with tolerance)
    // Bottom-left
    translate([magnet_x - magnet_tolerance, magnet_y - magnet_tolerance, magnet_z])
        cube([void_length, void_width, void_height]);

    // Bottom-right
    translate([magnet_x + compartment_size - magnet_tolerance, magnet_y - magnet_tolerance, magnet_z])
        cube([void_length, void_width, void_height]);

    // Top-left
    translate([magnet_x - magnet_tolerance, magnet_y + compartment_size - magnet_tolerance, magnet_z])
        cube([void_length, void_width, void_height]);

    // Top-right
    translate([magnet_x + compartment_size - magnet_tolerance, magnet_y + compartment_size - magnet_tolerance, magnet_z])
        cube([void_length, void_width, void_height]);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We export this code into and STL file, and examine it in the slicer:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//llm-reality-coding/img/tray-print-initial.png&quot; alt=&quot;tray print initial&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 14. Typical slicer program preview. The slider on the right allows to move through the layers, and the one on the bottom scrolls the printer nozzle&amp;#8217;s simulated position within a layer.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Here&amp;#8217;s how the layer transitions look like:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;videoblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;Transition of the print throughout the layers. Note the legend on the right.&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;video src=&quot;../../illustrations//llm-reality-coding/img/tray-slicer-preview.mp4&quot; width=&quot;900&quot; height=&quot;controls&quot; controls&gt;
Your browser does not support the video tag.
&lt;/video&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The magnet voids appear to be placed between layers 2 and 18 (inclusive), which, for a 0.3 mm layer height, means that there is indeed the correct amount of space. Note that is a &quot;Pause&quot; marker present at layer 18: this was done manually, to be able to insert the magnets without an oven-hot nozzle assembly, backed by surprisingly strong motors, hitting one&amp;#8217;s hand at speed.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Now, what&amp;#8217;s left is to print:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;videoblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;video src=&quot;../../illustrations//llm-reality-coding/img/tray-print-timelapse.mp4&quot; width=&quot;900&quot; height=&quot;controls&quot; controls&gt;
Your browser does not support the video tag.
&lt;/video&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//llm-reality-coding/img/tray-print-photo.jpeg&quot; alt=&quot;tray print photo&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;And does it work?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;videoblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;video src=&quot;../../illustrations//llm-reality-coding/img/tray-print-demo.mp4&quot; width=&quot;259&quot; height=&quot;controls&quot; controls&gt;
Your browser does not support the video tag.
&lt;/video&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;It does!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;code_review&quot;&gt;Code Review&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As a final opportunity for insights, let&amp;#8217;s go back to the various pieces of generated code, to see:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;what can we learn about the OpenSCAD language itself,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;what patterns are present, and how frequently they appear.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This is an area where the LLMs&apos; usual propensity for overcommenting actually works in our favor. In general, we can see that OpenSCAD offers a set of primitives used together to formulate the final assembly. There&amp;#8217;s also variables, and, for code organization, modules (functions).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Interestingly, &lt;code&gt;for&lt;/code&gt; loops also exist (&lt;a href=&quot;https://gist.github.com/mikolak-net/96f6fbfde61354d19bc9482e66a93349#file-openscad-tray-gpt41-fixed-scad-L32&quot; target=&quot;_blank&quot;&gt;example&lt;/a&gt;, cf. &lt;a href=&quot;https://en.wikibooks.org/wiki/OpenSCAD_Tutorial/Chapter_7&quot; target=&quot;_blank&quot;&gt;tutorial&lt;/a&gt;, also &lt;a href=&quot;https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/List_Comprehensions&quot; target=&quot;_blank&quot;&gt;list comprehensions&lt;/a&gt;), but are usually avoided, unless directly or indirectly prompted for. The net effect is the code becoming more verbose, and not actually always easier to follow. Furthermore, &quot;unrolling&quot; loops is the preferred style in &lt;a href=&quot;https://openscad.org/documentation.html&quot; target=&quot;_blank&quot;&gt;OpenSCAD&amp;#8217;s tutorial&lt;/a&gt; – which likely formed a significant part of the relevant training data available for OpenSCAD code.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The models also consistently make similar mistakes:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;introducing &quot;magic numbers&quot; instead of fixing every value into a variable,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;making &quot;origin-related&quot; offsetting errors,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&quot;forgetting&quot; or juxtaposing elements in the calculations (such as our GLM-4.5-Air code),&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&quot;fluffing up&quot; the code with unnecessary style elements (like colors),&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;etc.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Luckily, at least at the level of complexity we&amp;#8217;re dealing with, these mistakes are easy to spot by anyone with a half-decent eye for correctness in programs.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;outro&quot;&gt;Outro&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Hopefully, this exploration has provided not only a highlight of the fact that code-focused LLMs can generate infographics or functional 3D models, but also some measure of inspiration to check out what is also possible, without resorting to more specialized LLMs.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;It is also quite interesting that even vastly simpler, local models can mostly keep up with their cloud-based counterparts, at least for this type of task.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;On the flip side, the fact that everything covered here is ultimately expressed in some form of &lt;em&gt;code&lt;/em&gt; provides a distinct advantage over typical &quot;genAI&quot; fare. Code can be inspected and, most importantly, easily modified, unlike, say, a typical raster image. Even the verification element is extra-useful here, as such observations can be converted into follow-up prompts to correct the output (at the risk of being you&amp;#8217;re-absolutely-righted again).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;And that&amp;#8217;s about it for this article. I do encourage, in case anyone finds similar use cases, to share them in the comments.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_1&quot;&gt;
&lt;a href=&quot;#_footnoteref_1&quot;&gt;1&lt;/a&gt;. Graphics design is my passion 🐸.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_2&quot;&gt;
&lt;a href=&quot;#_footnoteref_2&quot;&gt;2&lt;/a&gt;. Neither is it completely representative of practical usage, e.g. the tokens-per-second performance varies considerably across models.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_3&quot;&gt;
&lt;a href=&quot;#_footnoteref_3&quot;&gt;3&lt;/a&gt;. The solids were, of course, spaced out during the export process.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_4&quot;&gt;
&lt;a href=&quot;#_footnoteref_4&quot;&gt;4&lt;/a&gt;. Yes, I do realize that&amp;#8217;s an oxymoron, thank you very much :).
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_5&quot;&gt;
&lt;a href=&quot;#_footnoteref_5&quot;&gt;5&lt;/a&gt;. which I would love to say was 3.7 Thinking, but, because Copilot arbitrarily cut access to that in the meantime, was actually 4.5
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Extracting Machine Learning Training Data from a Video Game – Using &quot;Ready-Mades&quot;</title>
      <link>https://xn--mikoak-6db.net/blog/2024/blog-extracting-data-3-ready-mades.html</link>
      <pubDate>Sat, 12 Oct 2024 00:00:00 +0200</pubDate>
      <guid isPermaLink="false">blog/2024/blog-extracting-data-3-ready-mades.html</guid>
      	<description>
	&lt;div id=&quot;toc&quot; class=&quot;toc&quot;&gt;
&lt;div id=&quot;toctitle&quot;&gt;Table of Contents&lt;/div&gt;
&lt;ul class=&quot;sectlevel1&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#starting_words&quot;&gt;Starting words&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#modern_detectiving_with_open_vocabulary_models&quot;&gt;Modern Detectiving with Open-Vocabulary Models&lt;/a&gt;
&lt;ul class=&quot;sectlevel2&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#intro&quot;&gt;Intro&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#implementation&quot;&gt;Implementation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#local_large_models_to_the_rescue&quot;&gt;Local Large Models to the rescue?&lt;/a&gt;
&lt;ul class=&quot;sectlevel2&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#moondream_initial_approach_and_examination&quot;&gt;Moondream: initial approach and examination&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#prompt_engineering_of_input_images&quot;&gt;&quot;Prompt engineering&quot; of input images&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#conclusions_for_moondream&quot;&gt;Conclusions for Moondream&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#do_we_even_need_the_designator_looking_into_auto_segmentation_via_sam_2&quot;&gt;Do we even need the designator? Looking into auto-segmentation via SAM 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#mainstream_llms&quot;&gt;&quot;Mainstream&quot; LLMs&lt;/a&gt;
&lt;ul class=&quot;sectlevel2&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#claude&quot;&gt;Claude&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#chatgpt&quot;&gt;ChatGPT&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#summary&quot;&gt;Summary&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/gotowce-splash-headliner.jpg&quot; alt=&quot;gotowce splash headliner&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Illustration adapted from photo by &lt;a href=&quot;https://unsplash.com/@seffen99?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash&quot;&gt;Sven Brandsma&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/a-woman-sitting-at-a-table-next-to-a-building-made-out-of-legos-sKbKehjZHW4?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash&quot;&gt;Unsplash&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;starting_words&quot;&gt;Starting words&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Hello again! We are continuing our series on extracting Machine Learning training data from a video game. In the previous blog entry, we&amp;#8217;ve run some exploratory data analysis on what we aim to extract for the &quot;final&quot; model&amp;#8217;s training. From now on, we&amp;#8217;ll be focusing on the actual extraction process of said data.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The theme of this particular post is &quot;ready-made&quot;. In other words, we&amp;#8217;re going to look at some relatively current methods to solve our problem – or side step it; ones that have a characteristic of doing most of the work for us. We&amp;#8217;ll start with a modern detection model, and then proceed with a local LLM (or rather, VLM), explore alternative sources of auto-derived detection training data, and compare all that with hosted LLMs from leading vendors.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;admonitionblock important&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td class=&quot;icon&quot;&gt;
&lt;i class=&quot;fa icon-important&quot; title=&quot;Important&quot;&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class=&quot;content&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The current entry is likely to be the least &quot;technically complex&quot; in the entire series, meaning it will be the easiest to replicate for an arbitrary person, and therefore adapt to other use cases. Something to keep in mind, especially if you, Dear Reader, stumbled upon this post randomly.&lt;/p&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;For a refresher on what we want to achieve, feel free to consult the starter blog entry, &lt;a href=&quot;https://mikołak.net/blog/2024/blog-extracting-data-1-intro.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;modern_detectiving_with_open_vocabulary_models&quot;&gt;Modern Detectiving with Open-Vocabulary Models&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;intro&quot;&gt;Intro&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&quot;Modern&quot; is, of course, an ill-defined term in the current breakneck-paced bazaar of ML solutions. However, we can provide a sensible generalization of what that could be at the moment of writing of this entry, at least for our specific problem scope.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Speaking of &lt;em&gt;specific&lt;/em&gt;, one relatively recent – i.e., no more than 2-year-old – trend for detection models is to enable operating on an &quot;open vocabulary&quot;, as opposed to a rigid set of classes. So, instead of telling the model to detect classes of objects like &quot;car&quot;, &quot;bicycle&quot;, and similar, the user can be more creative, and supply prompts such as &quot;yellow vehicle&quot;, or &quot;car with two windows&quot;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As already mentioned, open-vocabulary detection models have been developed for about 2 years now. A relatively fresh example, and one we&amp;#8217;ll use in the current section, is YOLO-World, released early 2024. True to its name, it is based on YOLO for detection, augmented by the ability to fuse text and image embeddings (representations of both in a numeric vector space). A detailed explanation is beyond the scope of this blog – for those interested, the original paper is available on &lt;a href=&quot;http://arxiv.org/abs/2401.17270&quot;&gt;arXiv&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We&amp;#8217;ll now see if we can wrangle YOLO-World to detect our designators. To juggle our memory from &lt;a href=&quot;https://mikołak.net/blog/2024/blog-extracting-data-2-ui-element-analysis.html&quot;&gt;the earlier entries&lt;/a&gt;, the designator is depicted in the center of the screenshot below, surrounding the targeted mech:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/gotowce-base-image.jpg&quot; alt=&quot;gotowce base image&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 1. The &quot;base image&quot; we&amp;#8217;ll use for detection and similar tasks, offering a relatively uncluttered scene with both the designator and a mech clearly in view.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;implementation&quot;&gt;Implementation&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The nice thing about testing out ML models in recent years is that, for the vast majority of use cases, everything is so, so convenient&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_1&quot; class=&quot;footnote&quot; href=&quot;#_footnote_1&quot; title=&quot;View footnote.&quot;&gt;1&lt;/a&gt;]&lt;/sup&gt;. Not only are there repositories that standardize model usage and deployment – most prominent &lt;a href=&quot;https://huggingface.co/&quot;&gt;Hugging Face&lt;/a&gt;, but they often come &quot;free&quot; with libraries further expediting the use of models present within their ecosystem. On top of &lt;em&gt;that&lt;/em&gt;, utility libraries exist that aggregate those &quot;commonized&quot; APIs for model usage, evaluation, and similar tasks into a meta-API, so that an arbitrary model authors&apos; choice of standards is not as much of a pain point as it was, say, 5 years ago (yes, yes, feel free to add your favorite joke on standards proliferation here).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;A library/framework that possesses the quality praised in the preceding paragraph, and one that we&amp;#8217;ll be using here, is &lt;a href=&quot;https://supervision.roboflow.com&quot;&gt;Supervision&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;So let&amp;#8217;s see how that goes; by &quot;that&quot; we mean using YOLO-World to find the target designator in the image.  We&amp;#8217;ll essentially be following along &lt;a href=&quot;https://supervision.roboflow.com/develop/notebooks/zero-shot-object-detection-with-yolo-world/&quot;&gt;one of Supervision&amp;#8217;s tutorials&lt;/a&gt;, with some small modifications:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;import cv2 as cv
import supervision as sv
from supervision import Position

from inference.models.yolo_world.yolo_world import YOLOWorld

detection_model = YOLOWorld(model_id=&quot;yolo_world/l&quot;)

# several different class prompts, in the hope that at least
# one will match for the designator
classes = [&quot;red rectangle&quot;, &quot;red outline&quot;, &quot;red box&quot;, &quot;red designator&quot;]

detection_model.set_classes(classes)

# setting up the annotators
BOUNDING_BOX_ANNOTATOR = sv.BoundingBoxAnnotator(thickness=2)
LABEL_ANNOTATOR = sv.LabelAnnotator(text_thickness=1, text_scale=0.5, text_color=sv.Color.WHITE, text_position=Position.BOTTOM_LEFT)

# loading the image shown above
frame = cv.imread(&quot;example_designator.jpg&quot;)

# we&apos;re using a very low confidence threshold, as we&apos;re
# interested in seeing &quot;what sticks&quot;
results = detection_model.infer(frame, confidence=1e-3)

# however, we are also still applying NMS, as potentially, in low
# confidence scenarios, we run the risk of being inundated with multiple,
# redundant, tiny detections
detections = sv.Detections.from_inference(results).with_nms(threshold=1e-4)

print(detections)

# will print out something like:
# Detections(xyxy=array([[     709.02,      829.93,      810.31,      1055.4],
#       [     810.56,      343.53,      879.66,      390.26],
#       [     799.74,       807.5,      1123.7,      1063.1],
#       [     809.68,      343.99,      879.36,      390.05]]),
#       mask=None,
#       confidence=array([  0.0019695,   0.0014907,   0.0012708,   0.0012423]),
#       class_id=array([2, 2, 2, 0]),
#       tracker_id=None,
#       data={&apos;class_name&apos;: array([&apos;red box&apos;, &apos;red box&apos;, &apos;red box&apos;, &apos;red rectangle&apos;], dtype=&apos;&amp;lt;U13&apos;)})


# this is, again, pretty much copied from the linked tutorial
# BTW, it is a bit unusual there&apos;s no sensible
# way to compose Supervision annotators

annotated_image = frame.copy()

labels = [
    f&quot;{classes[class_id]} {confidence:0.5f}&quot;
    for class_id, confidence
    in zip(detections.class_id, detections.confidence)
]


annotated_image = BOUNDING_BOX_ANNOTATOR.annotate(annotated_image, detections)
annotated_image = LABEL_ANNOTATOR.annotate(annotated_image, detections, labels=labels)
sv.plot_image(annotated_image, (30, 30))&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The final line results in the following image:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/gotowce-yolo-world-result-sample.png&quot; alt=&quot;gotowce yolo world result sample&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 2. Results of initial YOLO-World detection attempts.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Well, that&amp;#8217;s not looking great. The model, although powerful, evidently has trouble distinguishing various elements in our screenshot. The likely underlying reason is simple — the model was trained on datasets of &quot;actual&quot; images, i.e., photographs, and not on game screenshots&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_2&quot; class=&quot;footnote&quot; href=&quot;#_footnote_2&quot; title=&quot;View footnote.&quot;&gt;2&lt;/a&gt;]&lt;/sup&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Instead of looking at multiple (pre-)training datasets, we&amp;#8217;ll check out the one used for &lt;em&gt;evaluation&lt;/em&gt;, specifically &lt;a href=&quot;https://www.lvisdataset.org&quot;&gt;LVIS&lt;/a&gt;. Let&amp;#8217;s take a small look at that dataset to see what kind of text input data it provides. The spec is &lt;a href=&quot;https://www.lvisdataset.org/dataset&quot;&gt;here&lt;/a&gt; – we&amp;#8217;re interested in the &quot;Data Format&quot; → &quot;Categories&quot; section in particular. Let&amp;#8217;s also load up the training set and see what some example data looks like. Fire up this code and read the spec in the time it takes to load:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;# download the file from https://dl.fbaipublicfiles.com/LVIS/lvis_v1_train.json.zip
# and unzip it to the current directory

import pandas as pd
import json

with open(&quot;lvis_v1_train.json&quot;) as fp:
    lvis_training_instances = json.load(fp)

print(list(lvis_training_instances.keys()))
# prints out [&apos;info&apos;, &apos;annotations&apos;, &apos;images&apos;, &apos;licenses&apos;, &apos;categories&apos;]

categories_df = pd.DataFrame(lvis_training_instances[&quot;categories&quot;])

categories_df&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/gotowce-yolo-world-training-data-label-example.png&quot; alt=&quot;gotowce yolo world training data label example&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 3. Example of the LVIS dataset categories.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Feel free to explore both the names and the synonyms of the categories – the latter obtainable via a snippet like the following one&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_3&quot; class=&quot;footnote&quot; href=&quot;#_footnote_3&quot; title=&quot;View footnote.&quot;&gt;3&lt;/a&gt;]&lt;/sup&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;category_names_synonyms = categories_df[&apos;synonyms&apos;].explode().unique()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;It will become quickly apparent that the problem lies in, well, the &lt;em&gt;problem domain&lt;/em&gt; of the model – actual photographs of real-world objects. MechWarrior: Online is pretty far from being photorealistic (both due to stylistic choices and age), so the scenes in the screenshot can&amp;#8217;t always be meaningfully interpreted by the model in the context of its training data, even down to basic visual features. Demonstrating the latter problem is the following query, attempting to capture the red landing lights visible throughout the screenshot:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;classes = [&quot;red light&quot;, &quot;red lightbulb&quot;, &quot;landing light&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/gotowce-yolo-world-result-sample-diff-query-1.png&quot; alt=&quot;gotowce yolo world result sample diff query 1&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;None of the results remotely capture what we intended. The &quot;big&quot; detection is likely due to an association with a&amp;#8230;&amp;#8203; related concept, which we can verify by changing the classes appropriately:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;classes = [&quot;airplane&quot;, &quot;shuttle&quot;, &quot;vehicle&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/gotowce-yolo-world-result-sample-diff-query-2.png&quot; alt=&quot;gotowce yolo world result sample diff query 2&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Yep, the model actually does manage to &quot;recognize&quot; the &lt;a href=&quot;https://www.sarna.net/wiki/DropShip&quot;&gt;DropShip&lt;/a&gt; visible in the screenshot as an airplane&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_4&quot; class=&quot;footnote&quot; href=&quot;#_footnote_4&quot; title=&quot;View footnote.&quot;&gt;4&lt;/a&gt;]&lt;/sup&gt;. Note the considerably higher confidence – 0.8 is something on the level you would expect from an &quot;actual&quot; detection, as opposed to the unusually low confidence we used for the investigation.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Evidently, YOLO-World is actually capable of detecting artificially generated visuals, but not what we require&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_5&quot; class=&quot;footnote&quot; href=&quot;#_footnote_5&quot; title=&quot;View footnote.&quot;&gt;5&lt;/a&gt;]&lt;/sup&gt;. In fact, we&amp;#8217;ll see similar trends with other SotA (and non-SotA) models: virtually always, training datasets include COCO, Object365, and so on. The &quot;mainstream&quot; models are, in general, not prepared to operate on rendered images, at least not specifically so.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;So what can we do with this? One way to go is to adapt the model to our needs.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&amp;#8230;&amp;#8203;and this is what we would have proceeded with, had we not already declared that the blog entry will not be overtly technical. Instead, we&amp;#8217;ll perhaps revisit the adaptation task in another entry, but, for now, we&amp;#8217;ll just chalk this up as a lesson that even &quot;generalist&quot; models aren&amp;#8217;t general enough for &lt;strong&gt;each and every&lt;/strong&gt; use case – context still matters.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;local_large_models_to_the_rescue&quot;&gt;Local Large Models to the rescue?&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Fortunately, we still have several &quot;levels&quot; to go up on, the first one being trying out a more powerful, but still locally runnable&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_6&quot; class=&quot;footnote&quot; href=&quot;#_footnote_6&quot; title=&quot;View footnote.&quot;&gt;6&lt;/a&gt;]&lt;/sup&gt;, model.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/vikhyat/moondream&quot;&gt;Moondream&lt;/a&gt;, the one we&amp;#8217;ll use, is described as a &quot;Vision Language Model&quot;. Its inputs are both an image, and a text prompt. The latter can be anything from a simple &quot;describe this image&quot; request, to &quot;what is wrong with this specific object in the picture&quot; quasi-anomaly-detection. In between, we have an option to request detection bounding boxes for objects described in a freeform manner, and this is what we&amp;#8217;ll use.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As already alluded to, the model is relatively small for an LLM/VLM, and can easily be run on even a laptop-grade GPU, while still having decent time performance. The model is available on &lt;a href=&quot;https://huggingface.co/vikhyatk/moondream2&quot;&gt;Hugging Face&lt;/a&gt;, which makes getting it work a breeze. Curiously, there&amp;#8217;s little more information about it; publication-wise, the only references that can be found to the model come from comparison papers, &lt;a href=&quot;https://arxiv.org/abs/2403.17787&quot;&gt;such as this one&lt;/a&gt;&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_7&quot; class=&quot;footnote&quot; href=&quot;#_footnote_7&quot; title=&quot;View footnote.&quot;&gt;7&lt;/a&gt;]&lt;/sup&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;moondream_initial_approach_and_examination&quot;&gt;Moondream: initial approach and examination&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Regardless, let&amp;#8217;s get right to working on it, following the example code and our test screenshot, with some small modifications:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;from transformers import AutoModelForCausalLM, AutoTokenizer
from PIL import Image

# if GPU
DEVICE = &apos;cuda&apos;
# uncomment if GPU insufficient
# DEVICE = None

model_id = &quot;vikhyatk/moondream2&quot;
revision = &quot;2024-08-26&quot;
model = AutoModelForCausalLM.from_pretrained(
    model_id, trust_remote_code=True, revision=revision, device_map=DEVICE
)
tokenizer = AutoTokenizer.from_pretrained(model_id, revision=revision)

base_image = Image.open(&apos;blog/img/intro_interface_demo_raw/frame_5.jpg&apos;)


def infer(image, question):
    enc_image = model.encode_image(image)
    return model.answer_question(enc_image, question, tokenizer)


print(infer(base_image, &quot;Describe this image.&quot;))&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This will give us the following:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;exampleblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The image shows a screenshot from a video game, featuring a player&amp;#8217;s view of a futuristic environment with a large vehicle, mountains, and a control panel with various game elements.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Impressive, isn&amp;#8217;t it? Five years ago, models of this apparent analytical complexity, running on local hardware, would still be considered science-fiction.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;To cut the optimistic tone somewhat, note that we have little amounting to specifics – just a general description of the scene. The output does mention the hills in the background, and a vehicle, but that&amp;#8217;s about it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Wait a minute &apos;though: maybe the &quot;vehicle&quot; is what we need? To figure it out, we need to write a simple function that annotates the image with bounding boxes resultant from the model&amp;#8217;s output. And yes, the model &lt;strong&gt;is&lt;/strong&gt; capable of outputting bounding box coordinates. Here&amp;#8217;s the code:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;import ast
import numpy as np
import supervision as sv

def bbox_to_annotation(image, prompt):
    bbox_str = infer(image, prompt)

    # this is unsafe in general, especially when parsing
    # open-ended model outputs! Used here for demo purposes only.
    bbox = ast.literal_eval(bbox_str)

    # retrieve the xyxy coordinates
    x1, y1, x2, y2 = bbox

    # the coordinates are relative to the image size,
    # convert them to pixel values
    x1, x2 = (np.array([x1, x2]) * image.size[0]).astype(int)
    y1, y2 = (np.array([y1, y2]) * image.size[1]).astype(int)

    # create a Supervision Detections object with just our single bounding box
    detections = sv.Detections(np.array([[x1, y1, x2, y2]]), class_id=np.array([0]))

    # set up the annotator, which is a Supervision API for convenient
    # drawing, swapping, and composing various annotation types
    annotator = sv.BoxAnnotator()

    # need to copy the image – annotator works in-place by default!
    return annotator.annotate(image.copy(), detections)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;And here&amp;#8217;s the invocation with the result:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;bbox_to_annotation(base_image, &quot;Bounding box of the vehicle.&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/gotowce-extraction-frame-marking.png&quot; alt=&quot;gotowce extraction frame marking&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 4. Moondream&amp;#8217;s detection result for the base image and the &quot;vehicle&quot; prompt.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The &quot;vehicle&quot; turned out to be the large DropShip craft sitting on the runway, so that&apos; a miss.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Let&amp;#8217;s continue by exploring what the model &quot;sees&quot; in the vicinity of the target designator, as this might give a better idea of what vocabulary we might have to use for a successful prompt. First, directly, by cropping the image to just the designator.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/gotowce-moondream-designator-base-crop.png&quot; alt=&quot;gotowce moondream designator base crop&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 5. Image being analyzed below.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;designator_location_base_image = [860, 418, 955, 512]

base_image_designator_crop = sv.crop_image(base_image, xyxy=designator_location_base_image)
infer(base_image_designator_crop, &quot;Describe the image&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;exampleblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The image features a robot with a dark silhouette, standing in a red-framed area. The robot appears to be in a defensive stance, possibly ready to attack.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Apart from the actually internally consistent, but still amusing expression of &quot;defensive stance, [&amp;#8230;&amp;#8203;] ready to attack&quot;, two observations are of note here:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;the model does seem to actually recognize mechs as &quot;robots&quot;, which is impressive in and of itself;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;crucially for us, it also notices a &quot;red-frame area&quot;, i.e., our designator.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We&amp;#8217;ll eventually return to the former, proceeding now with the latter. We know that the model is able to associate the relevant UI element with text embedding in its encoding space that results in the &quot;red-frame&quot;. We should now determine how sensitive the model is to this &quot;stimulus&quot; when given a broader context. To do that in a low-tech fashion, we&amp;#8217;ll repeatedly run the inference on an image consisting of the designator, plus a variable bit of margin. The variability will span from a relatively small dilation factor of several pixels, up to most of the image in question.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The code to perform the task is pretty simple:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;def random_crop(image, base_bb, offsets_range_max: int):
    # convert to np.array to allow for vectorized operations
    bb_xyxy = np.array(base_bb)

    # define the &quot;directions&quot; in which the offsets are applied
    # first corner should go only &quot;up&quot; and &quot;left&quot;,
    # second corner should go only &quot;down&quot; and &quot;right&quot;
    offset_direction = [-1, -1, 1, 1]

    # generate the random offsets for all BB coordinates
    offsets = np.random.randint(0, offsets_range_max, size=len(base_bb))

    # perform the &quot;widening&quot; calculation on the original BB
    crop_box = bb_xyxy + offset_direction * offsets

    # ensure the resultant crop BB is within the image&apos;s bounds
    repeat_max_by = len(crop_box) // len(image.size)
    clipped_crop_box = np.clip(crop_box, 0, list(image.size) * repeat_max_by)

    return sv.crop_image(image, clipped_crop_box)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Here&amp;#8217;s an example of invocation and result:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;random_crop(base_image, designator_location_base_image, 100)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/gotowce-moondream-designator-random-crop.png&quot; alt=&quot;gotowce moondream designator random crop&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Using the function we just defined, we can now generate the descriptions in the manner we discussed with the following code:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;import pandas as pd
from tqdm import tqdm


def extract_descriptions(
    source_image,
    designator_xyxy,
    min_offset=1,
    max_offset=1000,
    interval=10,
    num_iter=10,
):
    description_data = []
    for offset in tqdm(range(min_offset, max_offset, interval)):
        for iter in range(num_iter):
            cropped_image = random_crop(source_image, designator_xyxy, offset)
            desc = infer(cropped_image, &quot;Describe the image.&quot;)
            description_data.append(
                {&quot;max_offset&quot;: offset, &quot;size&quot;: cropped_image.size, &quot;description&quot;: desc}
            )

    return pd.DataFrame(description_data)

description_df =  extract_descriptions(base_image, designator_location_base_image)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/mikolak-net/8de776946339c13448993c7503fc44e1&quot;&gt;Here is the CSV file&lt;/a&gt; of the results obtained after an example run.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/gotowce-moondream-designator-random-crop-result-base-bottom.png&quot; alt=&quot;gotowce moondream designator random crop result base bottom&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 6. Smallest&amp;#8230;&amp;#8203;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/gotowce-moondream-designator-random-crop-result-base-top.png&quot; alt=&quot;gotowce moondream designator random crop result base top&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 7.  &amp;#8230;&amp;#8203;and largest maximum offsets.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Looking over the data, it is clear that the model recognizes the designator as &lt;em&gt;some&lt;/em&gt; element, only for its importance to fall of the description inclusion threshold it in the context of the larger picture.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Regardless, it seems that the model recognizes the frame as a &quot;red&quot; &lt;em&gt;something&lt;/em&gt;, as evidenced by this diagram:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;import seaborn as sns

_SEARCH_FOR = &quot;red&quot;

# filter the rows that contain the search term
max_offset_with_red = description_df[
    description_df[&quot;description&quot;].str.contains(_SEARCH_FOR)
][&quot;max_offset&quot;].rename(f&quot;max_offset_{_SEARCH_FOR}&quot;)

# plot against all max offsets
sns.histplot(
    [max_offset_with_red, description_df[&quot;max_offset&quot;]],
    palette=[&quot;red&quot;, &quot;#bbbbbb&quot;],
    bins=10,
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/gotowce-moondream-designator-random-crop-result-base-hist-red.png&quot; alt=&quot;gotowce moondream designator random crop result base hist red&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 8. Histogram showing a distribution of the &lt;code&gt;max_offset&lt;/code&gt; values where the word &quot;red&quot; is contained in the result. Note how the frequency of the word&amp;#8217;s presence decreases with &lt;code&gt;max_offset&lt;/code&gt; &apos;s value – in other words, with the size of the visible area around the designator.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Let&amp;#8217;s run some aggregation now, so that we may see some trends in the output. We&amp;#8217;ll proceed with that by processing the descriptions into a quasi-canonical form, and find the word &quot;red&quot; in the description, along with two neighboring words on each side, and then group the results. For the NLP processing, we&amp;#8217;ll use &lt;a href=&quot;https://spacy.io/&quot;&gt;spacy&lt;/a&gt;, the documentation of which contains a very nice &lt;a href=&quot;https://spacy.io/usage/spacy-101&quot;&gt;usage primer&lt;/a&gt;, also explaining some basic NLP concepts.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;from functools import partial
from typing import Optional
import spacy

_RED_VICINITY = 2
_SEARCH_FOR = &quot;red&quot;
_FIELD_DESCRIPTION = &quot;description&quot;

nlp = spacy.load(&quot;en_core_web_sm&quot;)


def word_neighborhood(
    source: str, lemmatized_word: str, neighborhood_size: int
) -&amp;gt; Optional[str]:
    &quot;&quot;&quot;Takes a single description, runs basic NLP to obtain lemmatized sentences, and extracts
    `neighborhood_size` words around the `lemmatized_word`, including the latter.&quot;&quot;&quot;

    # run the basic NLP pipeline
    doc = nlp(source)

    try:
        # we assume there&apos;s only one sentence that has the word
        # not a fan of exception-driven logic, but it&apos;s cleaner in this case
        word_sentence = [
            s for s in doc.sents if any(t.lemma_ == lemmatized_word for t in s)
        ][0]
    except IndexError:
        return None

    # get the lemmatized version of the sentence, without
    # stopwords and punctuation
    processed_sentence = [
        t.lemma_ for t in word_sentence if not t.is_stop and t.is_alpha
    ]

    word_pos = processed_sentence.index(lemmatized_word)

    # an alternative would be to use the various Matcher facilities in spaCy,
    # but the chosen approach is a bit less cumbersome, doesn&apos;t require as much
    # knowledge of spacy to read, and we don&apos;t care for efficiency in this case
    return &quot; &quot;.join(
        processed_sentence[
            max(0, word_pos - neighborhood_size) : word_pos + neighborhood_size + 1
        ]
    )


def process_description(description_df: pd.DataFrame) -&amp;gt; pd.DataFrame:
    # apply the word neighborhood function to the description column
    with_red_vicinity = description_df.copy()
    with_red_vicinity[_FIELD_DESCRIPTION] = with_red_vicinity[_FIELD_DESCRIPTION].apply(
        partial(
            word_neighborhood,
            lemmatized_word=_SEARCH_FOR,
            neighborhood_size=_RED_VICINITY,
        )
    )

    # group by description and aggregate the other fields
    return (
        with_red_vicinity.groupby(_FIELD_DESCRIPTION)
        .agg(
            {
                &quot;max_offset&quot;: np.median,
                &quot;size&quot;: list,
                # Add other fields as needed
            }
        )
        .reset_index()
    )


with_red_vicinity_unique = process_description(description_df)

with_red_vicinity_unique&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/gotowce-moondream-designator-random-crop-result-base-processed-bottom.png&quot; alt=&quot;gotowce moondream designator random crop result base processed bottom&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 9. Top 20 grouped smallest offsets&amp;#8230;&amp;#8203;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/gotowce-moondream-designator-random-crop-result-base-processed-top.png&quot; alt=&quot;gotowce moondream designator random crop result base processed top&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 10.  &amp;#8230;&amp;#8203;and top 20 largest ones.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Unfortunately, we confirm the trend we were seeing in the &quot;raw&quot; data – the designator becomes less &quot;distinct&quot; in larger inputs.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In other words, we can try to use the &quot;small detail&quot; text for our BB prompt, but we will not get expected results:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;bbox_to_annotation(base_image, &quot;Bounding box of the red square frame border.&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/gotowce-moondream-designator-base-crop-with-target-bottom-crop.png&quot; alt=&quot;gotowce moondream designator base crop with target bottom crop&quot; width=&quot;640&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 11. The BB is at the very bottom-left, not where we expect it to be.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;prompt_engineering_of_input_images&quot;&gt;&quot;Prompt engineering&quot; of input images&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;An alternative way for us to make it easier for the model to &quot;focus&quot; on what we want is to limit the information available in the scene. To do that, we&amp;#8217;ll use the insights from &lt;a href=&quot;https://mikołak.net/blog/2024/blog-extracting-data-2-ui-element-analysis.html&quot;&gt;the previous entry&lt;/a&gt; to simply color-threshold the input image.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Specifically, we&amp;#8217;ll mask everything below the 90th percentile of the color threshold for the designators we&amp;#8217;ve analyzed, which is color value 177 for the red channel. One way to do this would be to use OpenCV&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_8&quot; class=&quot;footnote&quot; href=&quot;#_footnote_8&quot; title=&quot;View footnote.&quot;&gt;8&lt;/a&gt;]&lt;/sup&gt;. Here are the relevant code snippets and inference results:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;import cv2 as cv

def mask_red_channel_opencv(image, threshold=177):

    # using the inRange function, which is slightly more readable than
    # creating a mask just based on numpy operations, i.e., something like this:
    # mask = (image[:, :, 2] &amp;gt;= threshold).astype(np.uint8) * 255

    mask = cv.inRange(image, (0, 0, threshold), (255,) * image.shape[-1])

    return image &amp;amp; np.expand_dims(mask, axis=-1)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/gotowce-moondream-designator-base-crop-with-target-color-thresh.png&quot; alt=&quot;gotowce moondream designator base crop with target color thresh&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 12. Color thresholded image.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;infer(masked_image, &quot;Describe the image.&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;exampleblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The image is a screenshot from a video game featuring a dark background with various elements and graphics. It appears to be a screenshot from a space shooter game, possibly from the &quot;URBANMECHE K-9&quot; series.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;bbox_to_annotation(
    masked_image,
    &quot;Bounding box of the small red frame area.&quot;,
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/gotowce-moondream-designator-base-crop-with-target-color-thresh-des-box.png&quot; alt=&quot;gotowce moondream designator base crop with target color thresh des box&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 13. Designator-detection attempt after color thresholding.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;That&amp;#8217;s more like it, but still, not perfect. Moreover, the base image is pretty much the &quot;ideal&quot; screenshot. For a more busy scene&amp;#8230;&amp;#8203;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;masked_image_busy = sv.cv2_to_pillow(
        mask_red_channel_opencv(sv.pillow_to_cv2(busy_image))
    )&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/gotowce-moondream-designator-busy-crop-with-target-color-thresh.png&quot; alt=&quot;gotowce moondream designator busy crop with target color thresh&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 14. Color thresholded image - &quot;busy&quot; scene.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;infer(masked_image_busy, &quot;Describe the image.&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;exampleblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The image is a screenshot from a video game featuring a space theme, with various elements of a spaceship cockpit and a cityscape in the background.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;bbox_to_annotation(
    masked_image_busy,
    &quot;Bounding box of the small red frame area.&quot;,
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/gotowce-moondream-designator-busy-crop-with-target-color-thresh-des-box.png&quot; alt=&quot;gotowce moondream designator busy crop with target color thresh des box&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 15. In case you can&amp;#8217;t see the location of the bounding box – it&amp;#8217;s almost the entirety of the image.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;No dice here, either.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;conclusions_for_moondream&quot;&gt;Conclusions for Moondream&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Like with YOLO-World, it looks like there&amp;#8217;s simply a mismatch between what the model was trained to detect, and what we want from it. Perhaps there is a specific prompt that &lt;strong&gt;does&lt;/strong&gt; let the model identify the target designator flawlessly. I&amp;#8217;m sure that if there is, I will be made aware of it in 5 minutes of posting this blog entry. However, that prompt certainly can&amp;#8217;t be described as easily discoverable.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Again, similarly to YOLO-World, Moondream can certainly be fine-tuned to output what we want, but that is out of scope for this entry, as defined in the introduction.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;It would be remiss to leave our consideration of Moondream, and similar distilled LVMs, at this. One thing must be stressed – the models are considerably more powerful than what we&amp;#8217;ve shown so far.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Case in point:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;bbox_to_annotation(
    busy_image,
    &quot;Bounding box of the mech on the left.&quot;,
)

bbox_to_annotation(
    busy_image,
    &quot;Bounding box of the mech on the right.&quot;,
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/gotowce-moondream-designator-base-mech-left.png&quot; alt=&quot;gotowce moondream designator base mech left&quot; width=&quot;640&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/gotowce-moondream-designator-base-mech-right.png&quot; alt=&quot;gotowce moondream designator base mech right&quot; width=&quot;640&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The two prompt results show:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;Not only does the model readily recognize &quot;robot-like&quot; shapes;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;it also has an embedding mapping rich enough that it can directly map the phrase &quot;mech&quot; onto these shapes;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the detected area actually does roughly correspond to the mechs in the scene.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;All this is to say: Moondream, and models like it, are certainly powerful enough to potentially serve our &lt;strong&gt;main&lt;/strong&gt; purpose, i.e., detecting the mechs on the screen, with only some tweaking. We&amp;#8217;ll revisit this opportunity space in future entries.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;do_we_even_need_the_designator_looking_into_auto_segmentation_via_sam_2&quot;&gt;Do we even need the designator? Looking into auto-segmentation via SAM 2&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Throughout the current body of the blog series, we’ve operated on an assumption that extracting the designator images is our best bet of building a training set for the &quot;final&quot; mech detection model. Is that necessarily the case?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Those knowing a &lt;a href=&quot;https://en.wikipedia.org/wiki/Betteridge%27s_law_of_headlines&quot;&gt;certain &quot;law&quot;&lt;/a&gt; already know the answer. Indeed, one of the changes in recent years has been the appearance of modern generalized image segmentation models&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_9&quot; class=&quot;footnote&quot; href=&quot;#_footnote_9&quot; title=&quot;View footnote.&quot;&gt;9&lt;/a&gt;]&lt;/sup&gt;. The output thereof can be then used as training data for the target detection models.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We&amp;#8217;ll try out a relatively recent one, &lt;a href=&quot;https://github.com/facebookresearch/segment-anything-2&quot;&gt;Segment Anything 2&lt;/a&gt; from Meta (née Facebook), available alternatively &lt;a href=&quot;https://huggingface.co/models?search=facebook%2Fsam2&quot;&gt;on Hugging Face&lt;/a&gt;. There&amp;#8217;s also a rather impressive &lt;a href=&quot;https://ai.meta.com/sam2/&quot;&gt;landing page&lt;/a&gt;, with various demos, including interactive ones.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Perusing the docs, notebooks, etc., we should notice quite quickly what the major trait is of the model, setting it apart from the &quot;classical&quot; segmentation models. Namely, it can provide complete, or near-complete segmentation of an image without any other input, as evidenced by the screenshot below&amp;#8230;&amp;#8203;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/gotowce-sam2-unprompted-base-image.png&quot; alt=&quot;gotowce sam2 unprompted base image&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/gotowce-segmentation-example.png&quot; alt=&quot;gotowce segmentation example&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 16. Our base image, auto-masked by SAM 2. Generated by following example code from &lt;a href=&quot;https://github.com/facebookresearch/sam2/blob/98fcb164bf880f70799c324c283c758c4d20bf82/notebooks/automatic_mask_generator_example.ipynb&quot;&gt;this notebook&lt;/a&gt;, specifically the &quot;Automatic mask generation options&quot; section.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;div class=&quot;title&quot;&gt;For comparison: the same image, segmented &lt;a href=&quot;https://scikit-image.org/docs/stable/auto_examples/segmentation/plot_segmentations.html#slic-k-means-based-image-segmentation&quot;&gt;using SLIC&lt;/a&gt;, a technique introduced in 2012. Note that the segmentation provides our desired result as well in this case; in other words, the ability of SAM 2 to just fully segment the image to our spec is not the thing we&amp;#8217;re looking for.&lt;/div&gt;
&lt;p&gt;Its real power lies in the ability to segment images based on a point prompt. Well, that &lt;strong&gt;and&lt;/strong&gt; being able to process videos based on that original prompt (and being resilient against occlusions, and other things&amp;#8230;&amp;#8203;).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Let&amp;#8217;s see a little demonstration. The code here is heavily derived from &lt;a href=&quot;https://blog.roboflow.com/sam-2-video-segmentation/&quot;&gt;Roboflow&amp;#8217;s blog entry, authored by Piotr Skalski, on SAM 2&lt;/a&gt;, the primary adaptation being generalizing most of the logic into a single-function API&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_10&quot; class=&quot;footnote&quot; href=&quot;#_footnote_10&quot; title=&quot;View footnote.&quot;&gt;10&lt;/a&gt;]&lt;/sup&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;# again, this is mostly code from
# https://blog.roboflow.com/sam-2-video-segmentation/
# reorganized and slightly amended

import cv2 as cv
import numpy as np
import pandas as pd
import supervision as sv

from pathlib import Path

def get_masks_as_detections(object_ids, mask_logits) -&amp;gt; sv.Detections:
    &quot;&quot;&quot;Converts SAM 2 output to Supervision Detections.&quot;&quot;&quot;
    masks = (mask_logits &amp;gt; 0.0).cpu().numpy()
    N, X, H, W = masks.shape
    masks = masks.reshape(N * X, H, W)

    return sv.Detections(
        xyxy=sv.mask_to_xyxy(masks=masks), mask=masks, tracker_id=np.array(object_ids)
    )


def add_points_to_predictor(predictor, inference_state, point_data: pd.DataFrame):
    &quot;&quot;&quot;Add points to SAM 2&apos;s inference state.

    This function assumes it receives a DF with the columns:
    [&quot;point_xy&quot;, &quot;object_id&quot;, &quot;label&quot;, &quot;frame_id&quot;]

    where &quot;label&quot; is 1 or 0 for a pos/neg example.
    &quot;&quot;&quot;

    # aggregate by frame and object, to conform to batching modus
    # of predictor.add_new_points
    aggregated = point_data.groupby([&quot;frame_id&quot;, &quot;object_id&quot;]).agg(list)

    for (frame_id, object_id), row in aggregated.iterrows():

        points = np.array(row[&quot;point_xy&quot;], dtype=np.float32)
        labels = np.array(row[&quot;label&quot;])

        predictor.add_new_points(
            inference_state=inference_state,
            frame_idx=frame_id,
            obj_id=object_id,
            points=points,
            labels=labels,
        )

def ensure_frames_generated(source_video: Path, frame_dir_root: Path) -&amp;gt; Path:
    &quot;&quot;&quot;Checks if the frame images, necessary for SAM 2&apos;s state init and processing, are present.

    If not, generates and saves them to the corresponding `frame_dir_root` subdirectory.
    &quot;&quot;&quot;

    video_name = source_video.stem

    frame_dir = frame_dir_root / video_name

    # a simple check - if the video for the directory exists,
    # we assume the frames are already generated
    if not frame_dir.exists():
        sink = sv.ImageSink(target_dir_path=frame_dir, image_name_pattern=&quot;{:04d}.jpeg&quot;)

        with sink:
            for frame in sv.get_video_frames_generator(str(source_video)):
                sink.save_image(frame)

    return frame_dir


def segment_video(
    predictor,
    source_video: Path,
    point_data: pd.DataFrame,
    target_video: Path,
    label_annotator: sv.LabelAnnotator,
    mask_annotator: sv.MaskAnnotator,
    frame_dir_root: Path = Path(&quot;./video_frames&quot;),
):
    frame_dir = ensure_frames_generated(source_video, frame_dir_root)

    # init model&apos;s state on the video&apos;s frames
    inference_state = predictor.init_state(video_path=str(frame_dir))

    # add segment guidance points to the model
    add_points_to_predictor(predictor, inference_state, point_data)

    video_info = sv.VideoInfo.from_video_path(str(source_video))

    frames_paths = sorted(
        sv.list_files_with_extensions(directory=frame_dir, extensions=[&quot;jpeg&quot;])
    )

    with sv.VideoSink(str(target_video), video_info=video_info) as sink:
        for frame_i, object_ids, mask_logits in predictor.propagate_in_video(
            inference_state
        ):
            frame = cv.imread(str(frames_paths[frame_i]))

            detections = get_masks_as_detections(object_ids, mask_logits)

            frame_annotated = label_annotator.annotate(frame, detections)
            frame_annotated = mask_annotator.annotate(frame_annotated, detections)
            sink.write_frame(frame_annotated)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;which can be invoked like this:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;segment_video(
    predictor,
    source_video=Path(SOURCE_VIDEO),
    point_data=point_data,
    target_video=Path(TARGET_VIDEO_PATH),
    label_annotator=sv.LabelAnnotator(
        color=sv.ColorPalette.DEFAULT,
        color_lookup=sv.ColorLookup.TRACK,
        text_color=sv.Color.BLACK,
        text_scale=0.5,
        text_padding=1,
        text_position=sv.Position.CENTER_OF_MASS,
    ),
    mask_annotator=sv.MaskAnnotator(
        color=sv.ColorPalette.DEFAULT, color_lookup=sv.ColorLookup.TRACK
    ),
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The &lt;code&gt;predictor&lt;/code&gt; value is the result of a &lt;code&gt;sam2.build_sam.build_sam2_video_predictor&lt;/code&gt; call, the instructions for setting up which are available in the &lt;a href=&quot;https://github.com/facebookresearch/sam2/blob/98fcb164bf880f70799c324c283c758c4d20bf82/notebooks/video_predictor_example.ipynb&quot;&gt;relevant documentation&lt;/a&gt;. In all examples here, we are using the &quot;large&quot; model variant.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Since we&amp;#8217;re switching from images to video, we need to actually choose a video snippet. We&amp;#8217;ll go with this one:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;videoblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;iframe width=&quot;960&quot; height=&quot;720&quot; src=&quot;https://www.youtube.com/embed/F18OIzk4XrI?rel=0&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As you can see, the video can potentially be challenging to segment – the color palette is muted and homogenous, the objects of interest are either small, occluded, or blend with the background well. Nevertheless, let&amp;#8217;s try marking a single point – specifically on the very fist visible mech up in front – and see how SAM 2 performs:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;videoblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;iframe width=&quot;960&quot; height=&quot;720&quot; src=&quot;https://www.youtube.com/embed/IWR5Y9SfzWM?rel=0&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;That&amp;#8217;s actually impressive. The model does manage to keep up with both the objects and the camera&amp;#8217;s movement, only &quot;picking up&quot; some extraneous elements, but never extending the mask to an unacceptable level. The only bigger hangup is it losing the object and switching tracking to another one after the zoom level switch – again, nothing concerning, as there&amp;#8217;s no indication SAM 2 should be resilient against that.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;OK, so we&amp;#8217;ve covered a single object, but, in this snippet, a grand total of 5&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_11&quot; class=&quot;footnote&quot; href=&quot;#_footnote_11&quot; title=&quot;View footnote.&quot;&gt;11&lt;/a&gt;]&lt;/sup&gt; are available to segment. We extend the point data to the following form:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;import pandas as pd

# as a reminder, &quot;label&quot; denotes whether
# the example is &quot;positive&quot; or &quot;negative&quot;
#
# we identify our &quot;segmentation tracks&quot;
# by the &quot;object_id&quot; field

point_data = pd.DataFrame(
    [
        [[1122, 347], 1, 1, 52],
        [[1167, 348], 2, 1, 52],
        [[1030, 350], 1, 1, 76],
        [[880, 338], 2, 1, 113],
        [[900, 355], 2, 0, 113],
        [[1675, 435], 3, 1, 130],
        [[1508, 446], 4, 1, 131],
        [[1553, 444], 4, 0, 131],
        [[1867, 427], 5, 1, 145],
        [[1202, 391], 4, 1, 363],
        [[1258, 435], 3, 1, 363],
        [[755, 324], 2, 1, 393],
        [[1145, 399], 4, 1, 436],
        [[1155, 331], 4, 1, 436],
        [[1062, 383], 4, 1, 615],
        [[1011, 391], 3, 1, 636],
    ],
    columns=[&quot;point_xy&quot;, &quot;object_id&quot;, &quot;label&quot;, &quot;frame_id&quot;],
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;For reference and visualization, here is the representation of the example points on the respective frames, as well as the initial segmentation provided by SAM 2&amp;#8217;s model state.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/gotowce-sam2-frame-debug-grid.jpg&quot; alt=&quot;gotowce sam2 frame debug grid&quot; width=&quot;200&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 17. The debug frames. Note the subtitles. Example points are denoted as circles – filled for &quot;positive&quot;, hollow for &quot;negative&quot;. Masks generated from the model&amp;#8217;s inference state while adding the points are also provided. &lt;a href=&quot;img/gotowce-sam2-frame-debug-grid.jpg&quot;&gt;Click here for a larger version&lt;/a&gt;.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We can see that the static image masking does look promising. The biggest problem is that SAM 2 is a bit &quot;greedy&quot;, and often incorporates parts of the background – see especially the first frame presented – hence the need for several negative examples. We also had to provide additional example points for objects that &quot;started out&quot; adjacent, or even overlapping. Again, still a good show so far, given the difficulty of the input video.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;From the examples provided, we get the following segmentation results:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;videoblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;iframe width=&quot;960&quot; height=&quot;720&quot; src=&quot;https://www.youtube.com/embed/vlzffuo2H7o?rel=0&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Comparing to the single-point example, the most notable phenomenon is a markedly reduced tracking ability of the &quot;original&quot; object (denoted as &lt;code&gt;0&lt;/code&gt;). This is likely due to attempting simultaneous segmentation on another mech (denoted as &lt;code&gt;1&lt;/code&gt;) that&amp;#8217;s passing right behind &lt;code&gt;0&lt;/code&gt;. Overall, the model is unable to persistently track virtually any of the objects without further point examples, and even then, it eventually loses persistence. Interestingly, it does manage persist the tracking of one object over the zoom sequence (&lt;code&gt;4&lt;/code&gt;), which means SAM 2&amp;#8217;s anti-occlusion facilities are quite powerful.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Nevertheless, we must remind ourselves that we are not investigating SAM 2 for tracking capabilities, but to provide a source of training data for detection. And, for this purpose, it seems like a promising direction. The model produces masks that are almost always more precise than the equivalent of the target designator&amp;#8217;s shape (something we haven&amp;#8217;t talked about yet – even when we do get the designators, a cleanup step will still be necessary before we proceed with actual training, or fine-tuning, of a detection model).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Additionally, we&amp;#8217;ve imposed quite a challenge on SAM 2. There&amp;#8217;s plenty of &quot;real world&quot; use cases where the model would be likely to perform with much greater fidelity, and so provide even better annotation data.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The only gripe is resource consumption – even the &quot;tiny&quot; version of SAM 2 uses too much VRAM for most consumer GPUs. The hope lies in the larger community creating derivatives that require less memory, while having similar performance.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;mainstream_llms&quot;&gt;&quot;Mainstream&quot; LLMs&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;While things were a bit different even a year ago (due to lacking image-assisted prompt possibilities), it would now be remiss to not include a comparison of processing through the &quot;mainstream&quot; LLMs. We&amp;#8217;re going to take a look at two families of them: Anthropic&amp;#8217;s &lt;em&gt;Claude&lt;/em&gt; and OpenAI&amp;#8217;s &lt;em&gt;GPT-4o&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The plan is as follows – first, we&amp;#8217;re going to supply &lt;a href=&quot;img/intro_interface_demo_raw/frame_5.jpg&quot;&gt;the demo image&lt;/a&gt; with the following prompt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;exampleblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The image is a screenshot of a video game. On the screenshot, there is a target designator, shaped like a square with middle portions of its sides missing, all colored red. Provide the bounding box pixel coordinates of this designator.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We&amp;#8217;re going to repeat the same text prompt with the &lt;a href=&quot;img/intro_screenshot_processed.png&quot;&gt;&quot;busy&quot; image&lt;/a&gt;. Finally, we&amp;#8217;re going to use one of the frame images from the test video, i.e., this one:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/gotowce-reference-frame-145.png&quot; alt=&quot;gotowce reference frame 145&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 18. The reference frame we will be using, or &quot;the third image&quot;.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;and the prompt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;exampleblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The image is a screenshot from a video game in the BattleTech/MechWarrior franchise. Provide bounding box locations of all the mechs in the image.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We&amp;#8217;ll adapt this preliminary plan in case a potential for additional insights manifests itself.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;claude&quot;&gt;Claude&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Spoiler: this won&amp;#8217;t go well. Starting with demo image and the specified prompt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;exampleblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;I apologize, but I&amp;#8217;m not able to provide exact pixel coordinates from the image. However, I can describe the location and appearance of the red target designator you&amp;#8217;re referring to:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The red target designator is located roughly in the center of the image, slightly below the midpoint vertically. It&amp;#8217;s surrounding what appears to be a mech or large robotic figure standing on a road or runway.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The designator consists of a square shape with the middle portions of each side missing, creating a crosshair-like effect. Inside this designator is a distance readout showing &quot;478m&quot;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;If you need precise pixel coordinates, &lt;strong&gt;you would need to use an image editing tool or specialized software to analyze the image&lt;/strong&gt;. I can only provide descriptive information based on what I see in the image.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;(emphasis added)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;So, unfortunately, it looks like Claude immediately turns out to be a dud for our purposes. Now, it&amp;#8217;s not all bad – it does recognize both the designator &lt;strong&gt;and the mech&lt;/strong&gt; in the image. We note also that Claude considers the distance readout as being &lt;em&gt;inside&lt;/em&gt; the designator; we&amp;#8217;ll get back to this observation in the ChatGPT section.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Just not to end Claude&amp;#8217;s exploration too abruptly, let&amp;#8217;s see what it has to say for some other images and prompts. Here&amp;#8217;s what it outputs when provided the &quot;busy&quot; image, with an adapted prompt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/gotowce-claude-busy-image.png&quot; alt=&quot;gotowce claude busy image&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Unfortunately, Claude doesn&amp;#8217;t seem to recognize the mech that&amp;#8217;s actually being targeted – there was a bit of hope the designator itself would provide sufficient context. At least it does identify the two other relevant objects in view. However, it describes the one on the left erroneously as hostile – quite obviously, due to the &quot;friendly&quot; marker above visually separated from the mech by one of the UI elements.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Interestingly, Claude does appear to have some information about the franchise, as it correctly references the existence of the assault weight class&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_12&quot; class=&quot;footnote&quot; href=&quot;#_footnote_12&quot; title=&quot;View footnote.&quot;&gt;12&lt;/a&gt;]&lt;/sup&gt;. Not surprising, &apos;though – BattleTech is enjoying its 40-year anniversary right about now.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Finally, let&amp;#8217;s see if Claude can at least recognize mechs in a more challenging screenshot:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/gotowce-claude-result-frame-145.png&quot; alt=&quot;gotowce claude result frame 145&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Disappointing as well: the correct answer is 5, not counting the player.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Before we conclude, we must note that the prompts were answered by the free version of Sonnet. While this may seem unfair, Claude all but disqualified itself by refusing to provide bounding box coordinates, apparently &lt;strong&gt;by design&lt;/strong&gt;. Frankly, the author is unwilling to splurge &amp;gt;20€ just to check if the Pro version has a different system pre-prompt.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Otherwise, it&amp;#8217;s worth pointing out that Claude, for our use cases, appears to have similar capabilities to a model such as Moondream.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;chatgpt&quot;&gt;ChatGPT&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We&amp;#8217;re using &lt;em&gt;GPT-4o&lt;/em&gt;, as &lt;em&gt;o1-preview&lt;/em&gt; doesn’t seem able to ingest images currently. For the demo image, using the prompt specified in the section intro, the model &lt;em&gt;does&lt;/em&gt; provide a bounding box: &lt;code&gt;(810, 370, 960, 520)&lt;/code&gt;. The coordinates translate to the following clip:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/gotowce-chatgpt-base-image.png&quot; alt=&quot;gotowce chatgpt base image&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Partially true, but not exact. The model seems to consider the distance readout as part of the designator. This quirk is not only present here but also in Claude &lt;em&gt;and&lt;/em&gt; in Moondream. Honestly, not entirely sure what is the reason. The best guess is these sorts of UI elements, (i.e., designators) indeed often have supplementary icons or text &quot;attached&quot; to them, and this is reflected in whatever common data sets were used to train the models. So, for the model, the designator indeed comprises both the element itself and any &quot;add-ons&quot;, an effect similar to the well-known &quot;huskies are on snowy background&quot; class of error – if it even can be considered an &quot;error&quot;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;A follow-up prompt:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;exampleblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The bounding box provided is too large. It should only contain the designator&amp;#8217;s square shape itself. Please refine the coordinates.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;yields &lt;code&gt;(842, 406, 927, 491)&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/gotowce-chatgpt-base-image-refined.png&quot; alt=&quot;gotowce chatgpt base image refined&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;better, but still not ideal.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Let&amp;#8217;s move on to the busy image, where the bounding box is given as &lt;code&gt;(617, 32, 1775, 1055)&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/gotowce-chatgpt-busy-image.png&quot; alt=&quot;gotowce chatgpt busy image&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 19. Yes, this is the &quot;bounding box&quot; provided by ChatGPT for the &quot;busy&quot; image.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;which, as we already guess from the coordinates alone, is way too imprecise – although, in fairness, it does include the designator itself. Funnily enough, the model, when requesting refinement, eventually &quot;gives itself up&quot; with this choice snippet:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;exampleblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;It appears that my current attempt still did not yield the correct bounding box for the smaller red designator. Given the complexity of identifying the exact designator in this image, I would recommend a more manual analysis or specialized visual inspection using specific graphic tools. If you prefer, you could manually examine pixel clusters using an image editor to isolate the exact region you&amp;#8217;re interested in.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;All right, let&amp;#8217;s do some follow-ups with &quot;smarter&quot; queries, like we did with Claude.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/gotowce-chatgpt-busy-image-mech-bbs.png&quot; alt=&quot;gotowce chatgpt busy image mech bbs&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 20. Result of the BB query on the &quot;busy&quot; image.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;For the &quot;BB location&quot; prompt (the second one from the intro), ChatGPT seems to provide similar results as Moondream. Note that the image above was generated &quot;manually&quot;, which is actually not strictly necessary – 4o absolutely can, and will, when asked for, draw the bounding boxes automatically.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/gotowce-chatgpt-busy-image-count.png&quot; alt=&quot;gotowce chatgpt busy image count&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 21. Result of an object-counting query on the &quot;busy&quot; image.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/gotowce-chatgpt-frame-145-count.png&quot; alt=&quot;gotowce chatgpt frame 145 count&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 22. Result of a similar query on the reference video frame.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;ChatGPT returns similar results as Claude. When pressed with the suggestion of the correct answer (5), and asked to provide bounding boxes, it does so, but hallucinates heavily, and returns completely incorrect BBs&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_13&quot; class=&quot;footnote&quot; href=&quot;#_footnote_13&quot; title=&quot;View footnote.&quot;&gt;13&lt;/a&gt;]&lt;/sup&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/gotowce-chatgpt-frame-145-bbs.png&quot; alt=&quot;gotowce chatgpt frame 145 bbs&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 23. Yeah, no.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We have undergone quite a journey in this post. What then, are its takeaways?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The primary one, perhaps: for specialized use cases, such as, apparently, ours, there is still no free lunch. Most popular vision models are trained principally on &quot;real world&quot; images, with &quot;real world&quot; objects. No wonder – most of the use cases need those. For others, however, non-trivial work needs to be done, at least in the form of fine-tuning. Bummer on the one hand, yet on the other, a confirmation that a lot of interesting problem-solving is still there to explore.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The second takeaway flows from the first: even the models of the &quot;large&quot; corps, trained while burning through the equivalent electricity consumption of a small country (&lt;a href=&quot;https://www.theregister.com/2024/10/04/texas_dc_power/&quot;&gt;not&lt;/a&gt; &lt;a href=&quot;https://www.theregister.com/2024/01/22/google_deepmind_train_alphageometry_system/&quot;&gt;an&lt;/a&gt; &lt;a href=&quot;https://www.theregister.com/2023/10/11/ai_processing_electricity/&quot;&gt;exaggeration&lt;/a&gt;), are unable to give answers to our problems for &quot;free&quot; – not because they are not deficient or borderline sci-fi at times, but they simply weren&amp;#8217;t trained for this. And speaking of &quot;free&quot;, were they even capable of performing the required tasks flawlessly, they&amp;#8217;d still be prohibitively expensive for such a hobby use case – &lt;a href=&quot;https://docs.anthropic.com/en/docs/build-with-claude/vision#calculate-image-costs&quot;&gt;we&amp;#8217;re talking dollars on the thousands of images/frames&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Finally, and most importantly, some conclusions relevant to the &quot;offline&quot; models we&amp;#8217;ve gone through:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;YOLO-World seemed to be the weakest for our use case, but even it can probably be made to work well with some fine-tuning, and not only for finding designators, but for mechs themselves. The latter, of course, presents a chicken-and-egg problem, as we need the data &lt;strong&gt;somehow&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Distilled VLMs such as Moondream work even better, and, perhaps in the near future, will be good enough to employ for the use case of detecting video-game-specific objects &lt;em&gt;without&lt;/em&gt; any fine-tuning. We must, however, be mindful of their complexity and resource requirements, barely runnable on the majority of consumer-grade hardware. We cannot then unequivocally settle on them just yet.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Modern generalist segmentation models such as SAM 2 offer the most intriguing opportunity – sure, they&amp;#8217;re not as convenient as auto-detecting target designators, but:&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;they potentially output more precise annotation information via the masks;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;with a couple of days of work, the input data yield can surpass the autodetection approach, as we can locate and annotate arbitrary objects on the screen, not just the ones surrounded by a specific UI element;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;however, we will need to wait for less power-hungry derivatives for this to work in an acceptable timeframe.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Overall, we&amp;#8217;re still not quite in the singularity that&amp;#8217;s being heralded as coming soon for some time now, not even for &quot;simple&quot; tasks such as computer vision. But that&amp;#8217;s good! It means there&amp;#8217;s still a lot of interesting work to be done, and a lot of interesting problems to solve.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In the next entries, we will proceed with that problem-solving. We&amp;#8217;ll be using several approaches to &lt;em&gt;precisely&lt;/em&gt; extract the target designators, and we will start doing what we should have been doing already – comparing the performance and resource usage of various approaches in a diligent manner. We probably will even revisit some of the models (or their derivatives) discussed here. For now – until next time!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_1&quot;&gt;
&lt;a href=&quot;#_footnoteref_1&quot;&gt;1&lt;/a&gt;. &amp;#8230;&amp;#8203;except for getting the models to play nice with drivers, interfacing libs such as CUDA, and so on. War, war never changes&amp;#8230;&amp;#8203;
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_2&quot;&gt;
&lt;a href=&quot;#_footnoteref_2&quot;&gt;2&lt;/a&gt;. See section 4.2 &quot;Pre-Training&quot; &lt;a href=&quot;http://arxiv.org/abs/2401.17270&quot;&gt;of the paper&lt;/a&gt; for the full collection of datasets.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_3&quot;&gt;
&lt;a href=&quot;#_footnoteref_3&quot;&gt;3&lt;/a&gt;. There&amp;#8217;s also the &lt;code&gt;def&lt;/code&gt; column that can be wrangled with some basic NLP, but that is unlikely to bring any novel or contradicting conclusions.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_4&quot;&gt;
&lt;a href=&quot;#_footnoteref_4&quot;&gt;4&lt;/a&gt;. It also manages that if you just provide &quot;dropship&quot; as a class, funnily enough.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_5&quot;&gt;
&lt;a href=&quot;#_footnoteref_5&quot;&gt;5&lt;/a&gt;. And no, it also won&amp;#8217;t detect a &quot;mech&quot; or &quot;robot&quot;, at least in this screenshot.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_6&quot;&gt;
&lt;a href=&quot;#_footnoteref_6&quot;&gt;6&lt;/a&gt;. Without paying for hardware worth as much as a good new car, that is.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_7&quot;&gt;
&lt;a href=&quot;#_footnoteref_7&quot;&gt;7&lt;/a&gt;. From March this year, so already somewhat out of date, of course.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_8&quot;&gt;
&lt;a href=&quot;#_footnoteref_8&quot;&gt;8&lt;/a&gt;. We could, of course, just use Pillow, as that&amp;#8217;s what we&amp;#8217;re starting with, but OpenCV&amp;#8217;s API is slightly less cumbersome for the particular use case.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_9&quot;&gt;
&lt;a href=&quot;#_footnoteref_9&quot;&gt;9&lt;/a&gt;. Not that generalized segmentation models or algorithms didn&amp;#8217;t exist before – quite the opposite, of course. It&amp;#8217;s just that the current crop is considerably more effective.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_10&quot;&gt;
&lt;a href=&quot;#_footnoteref_10&quot;&gt;10&lt;/a&gt;. Ideally, for actual usage, the code should have been generalized to something like a Builder-pattern object. That would render the code a bit less digestible for the purpose of a blog-contained example, however.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_11&quot;&gt;
&lt;a href=&quot;#_footnoteref_11&quot;&gt;11&lt;/a&gt;. Not counting the Rifleman II mech visible at the very end, crossing a gorge.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_12&quot;&gt;
&lt;a href=&quot;#_footnoteref_12&quot;&gt;12&lt;/a&gt;. And the mech in question &lt;strong&gt;is&lt;/strong&gt; an assault-class mech to boot.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_13&quot;&gt;
&lt;a href=&quot;#_footnoteref_13&quot;&gt;13&lt;/a&gt;. For completeness, the prompt was: &quot;The answer is actually 5 (five), not counting the player&amp;#8217;s mech. Can you provide bounding box locations of these five mechs?&quot;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Extracting Machine Learning Training Data from Video Games – UI element analysis for model training preparation</title>
      <link>https://xn--mikoak-6db.net/blog/2024/blog-extracting-data-2-ui-element-analysis.html</link>
      <pubDate>Mon, 10 Jun 2024 00:00:00 +0200</pubDate>
      <guid isPermaLink="false">blog/2024/blog-extracting-data-2-ui-element-analysis.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/designator_analysis_splash.jpg&quot; alt=&quot;designator analysis splash&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 1. Illustration adapted from photo by &lt;a href=&quot;https://unsplash.com/@gpthree&quot;&gt;George Pagan III&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/four-red-bars-bIlaTxx4nCo&quot;&gt;Unsplash&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;intro&quot;&gt;Intro&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Welcome to the new entry on our series oriented around ML in video game use cases. The entry forms a part of a loosely connected series, &lt;a href=&quot;https://mikołak.net/blog/2024/blog-extracting-data-1-intro.html&quot;&gt;the first of which&lt;/a&gt; sets up an overview of the problem space, the rationale, and the rough outline of how we&amp;#8217;ll proceed. You can read it &lt;a href=&quot;https://mikołak.net/blog/2024/blog-extracting-data-1-intro.html&quot;&gt;here&lt;/a&gt; if you want – but if you don&amp;#8217;t, here&amp;#8217;s a tl;dr:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;we&amp;#8217;re concerning ourselves with creating a situation-awareness-improving tool for a sci-fi, simulator-styled, multiplayer, First-Person Shooter, i.e., MechWarrior Online (MWO);&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;our task for now is to identify the position of the target designator on a frame (if present), and extract the image of the target contained within;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;we&amp;#8217;ll use this capability to extract training data (images of targets) to train our &quot;final&quot; model, which will serve as a tool for reviewing gameplay footage, for the purpose of improving situational awareness of players.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As a refresher, the following animation shows what the target designator looks like in the game (marked as 3.):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;videoblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;video src=&quot;../../illustrations//mwo_training_data_extraction/img/interface_demo.mp4&quot; autoplay controls loop&gt;
Your browser does not support the video tag.
&lt;/video&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;and, in contrast, an example of an &quot;actual&quot;, in-match screenshot:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/intro_screenshot_processed.png&quot; alt=&quot;intro screenshot processed&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 2. In-match screenshot, &quot;slightly&quot; more busy. PPI has been obfuscated by box-bluring.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Our general process for the series can be broken down as follows:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;we&amp;#8217;ll first examine a number of different methods for extracting the designator&amp;#8217;s position, including hyperparameter optimization, if applicable;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;after that, we&amp;#8217;ll run those methods on a larger dataset, comparing both their efficacy and efficiency.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Somewhere between 1 and 2, we&amp;#8217;ll also need to develop a quality measure for extracted images, to minimize the amount of bogus or low-quality training data, such as one in the screenshot below.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/target_selector_obscured.png&quot; alt=&quot;target selector obscured&quot; width=&quot;1920&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 3. An example of a bogus data point. Terrain completely obscures target&amp;#8217;s image.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;However, in this particular blog entry, we&amp;#8217;ll focus on Point 0, namely: what makes a target designator in MWO? We&amp;#8217;ll explore this question with the use of some basic Data Science methods.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;getting_started&quot;&gt;Getting started&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We&amp;#8217;ll mostly be using &lt;a href=&quot;https://opencv.org/&quot;&gt;OpenCV&lt;/a&gt; for frame loading and manipulation, as well as &lt;a href=&quot;https://numpy.org/&quot;&gt;numpy&lt;/a&gt; for numeric operations on the frame&amp;#8217;s data. Scikit-learn and other (deep) learning frameworks will come into play later.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This post assumes basic knowledge of all of the above (if you&amp;#8217;d like an overview of OpenCV basics, &lt;a href=&quot;https://www.reasonfieldlab.com/post/what-is-opencv-used-for&quot;&gt;here is one&lt;/a&gt;).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Having said that, let&amp;#8217;s establish some standards we&amp;#8217;ll be following in this and subsequent entries:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;import cv2 as cv

# assuming we:
# - loaded frames from cv2.VideoCapture into a `video` array
# - the video in question is 1080p, and in color
frame = cv.cvtColor(video[frame_index], cv.COLOR_BGR2RGB)

print(frame.shape) # (1080, 1920, 3)
print(frame.type) # dtype(&apos;uint8&apos;)
print(frame.max()) # 255&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In other &quot;words&quot;, unless stated otherwise:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;we&amp;#8217;ll be using the RGB colorspace (after all, anyone using OpenCV for any length of time has an &quot;amusing&quot; story of accidentally using its BGR default);&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;our color values will be in the &lt;code&gt;0-255&lt;/code&gt; range.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;designating_the_designator&quot;&gt;Designating the designator&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We&amp;#8217;re now looking for distinguishing features of the target designator, so that we can use that information to extract it from the input videos&apos; frames in later, upcoming blog entries.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;admonitionblock note&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td class=&quot;icon&quot;&gt;
&lt;i class=&quot;fa icon-note&quot; title=&quot;Note&quot;&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class=&quot;content&quot;&gt;
Fair warning about this section – we&amp;#8217;ll be going pretty deep here, and sometimes into seemingly redundant paths. This is to show more possibilities than just the &quot;optimal&quot; solution for this particular case.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Going back to the matter at hand – looking at the last screenshot again, it becomes pretty obvious the distinctive quality of the target designator is its color. In fact, let&amp;#8217;s see a couple more examples of the designators:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/selector_examples.png&quot; alt=&quot;selector examples&quot; width=&quot;480&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 4. Designator examples in various situations, 1:1 size.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We can observe a couple of things:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;the boxes are:&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;of size 96px by 96px, at least in the input samples are using;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;in general, very red,&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;they are, however, not uniformly red, due to blending at the borders, video encoding particularities, and a slight translucency applied throughout;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the target designators can be, themselves, obscured by other UI elements, like the ring (arm) and crosshair (torso) reticles,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;they are also not the only elements that appear to have this particular color – in two of the examples, we can see a third kind of reticle for lock-on weapons (no one said this game isn&amp;#8217;t complex!) that, in its active state, has visually the same hue.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Let&amp;#8217;s see how distinguishable that color is among the various reticles. The first kind of visualization tool that may come to mind is a histogram. We&amp;#8217;ll use the following functions to generate them:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;import numpy as np
import pandas as pd
import seaborn as sns

PIXEL_VALUE_LIMITS = (0, 255)


def to_channel_values_in_rows(image):
    channel_width = image.shape[-1]

    return np.moveaxis(image, len(image.shape)-1, 0).reshape([channel_width, -1])

def histogram_from_image(image, plt_axis, labels, colors, max_samples):
    channel_values_in_columns = np.transpose(to_channel_values_in_rows(image))

    hist_data = pd.DataFrame(data=channel_values_in_columns, columns=labels)

    sns.histplot(hist_data, ax=plt_axis, palette=colors, binwidth=10)
    plt_axis.set_ylim(0, max_samples) # necessary for consistency across all images&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Instead of using matplotlib histograms, we&amp;#8217;re going for &lt;a href=&quot;https://seaborn.pydata.org/generated/seaborn.histplot.html&quot;&gt;seaborn&amp;#8217;s version&lt;/a&gt; instead. This allows to more concisely define the graph parameters such as the colors and labels for each data element. We also need to extract the actual value frequencies from each color channel for the histogram to make sense – that&amp;#8217;s where the &lt;code&gt;to_channel_values_in_rows&lt;/code&gt; function comes in, converting the &lt;code&gt;[y][x][channel] –&amp;gt; value&lt;/code&gt; mapping of the image into an array of dimension &lt;code&gt;(channel_width, width*height)&lt;/code&gt;, where every row lists the intensity values of pixels for the particular channels.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;For an RGB histogram, we invoke the function like so:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;histogram_from_image(image, axs_from_matplotlib, [&quot;r&quot;, &quot;g&quot;, &quot;b&quot;], [&quot;r&quot;, &quot;g&quot;, &quot;b&quot;], 96**2 * 0.5)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The &lt;code&gt;max_samples&lt;/code&gt; is derived from the size of the image (target designator size), but ultimately something obtained via trail-and-error.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;OK, let&amp;#8217;s see what we got:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/selector_full_histogram_rgb.png&quot; alt=&quot;selector full histogram rgb&quot; width=&quot;2880&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 5. Sample target designators with corresponding RGB histograms.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;That&amp;#8217;s not&amp;#8230;&amp;#8203; very helpful, is it? The values are seemingly all over the place, we can mayyyybe make out a small bump in the R-channel&amp;#8217;s values around 200, but that&amp;#8217;s it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We mustn&amp;#8217;t give up on histograms quite yet, &apos;though. RGB is not the only colorspace. Alternatives include HSL and HSV colorspaces that, as the &lt;a href=&quot;https://en.wikipedia.org/wiki/HSL_and_HSV&quot;&gt;linked Wikipedia page states&lt;/a&gt;, align more closely with human visual perception than RGB.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Onto the histograms:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;histogram_from_image(cv.cvtColor(image, cv.COLOR_RGB2HLS), axs_from_matplotlib, [&quot;H&quot;, &quot;L&quot;, &quot;S&quot;], [&quot;black&quot;, &quot;magenta&quot;, &quot;cyan&quot;], 96**2 * 0.5)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/selector_full_histogram_hsl.png&quot; alt=&quot;selector full histogram hsl&quot; width=&quot;2880&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 6. Same target designators with their HSL histograms&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Immediately, we see that all diagrams have a distinctive peak in the Hue channel, within the 170-180 bin. So, what does, say, 175 at max saturation and half lightness (both for better color visibility) looks like? Like this:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;SWATCH_IMAGE_SIZE = 40
COMPONENT_VALUE_MAX = PIXEL_VALUE_LIMITS[1]  # 255 in our case


def display_color_swatch(h, l, s, image_size=SWATCH_IMAGE_SIZE):
    demo_image = np.tile([h, l, s], (image_size, image_size, 1)).astype(&apos;uint8&apos;)

    # housekeeping to ensure image is displayed in 1:1 ratio
    figsize(image_size * px, image_size * px)
    plt.axis(&apos;off&apos;)
    plt.tight_layout(pad=0)

    plt.imshow(cv.cvtColor(demo_image, cv.COLOR_HLS2RGB))

    plt.show()


h = 175
s = COMPONENT_VALUE_MAX
l = COMPONENT_VALUE_MAX / 2

display_color_swatch(h, l, s)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/hls_color_demo.png&quot; alt=&quot;hls color demo&quot; width=&quot;40&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Yeah, that does look red all right.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;admonitionblock note&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td class=&quot;icon&quot;&gt;
&lt;i class=&quot;fa icon-note&quot; title=&quot;Note&quot;&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class=&quot;content&quot;&gt;
In the previous couple of snippets, we&amp;#8217;ve been using HLS (instead of HSL) since that what OpenCV offers (similarly to BGR vs RGB). Keep that in mind, so as not to mix up the channels.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;For good measure, let&amp;#8217;s take a portion of each of the boxes – in this case, the &quot;upper-left&quot; one, i.e. &lt;code&gt;image[8:37, 7:10, :]&lt;/code&gt;, and generate the histograms for that:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/selector_corner_histograms.png&quot; alt=&quot;selector corner histograms&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This indicates even more strongly that just going by hue value might be our ticket (since the same &quot;spike&quot; is visible right around the 170-180 bin).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;To preempt the eventuality that masking by a single channel value might be insufficient, we can examine the relationship between the different channels. At first, visually. We need a &lt;a href=&quot;https://matplotlib.org/stable/gallery/mplot3d/scatter3d.html&quot;&gt;3D scatter plot&lt;/a&gt;. Our function to generate one looks like this:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;AX3D_PREFIX_SETTER = &quot;set_&quot;
AX3D_AXES = [&quot;x&quot;, &quot;y&quot;, &quot;z&quot;]
AX3D_LIM_SUFFIX = &quot;lim3d&quot;
AX3D_LABEL_SUFFIX = &quot;label&quot;


def set_up_3d_plot_limits_and_labels(ax, labels: tuple[str, str, str]):
    &quot;&quot;&quot;Helper function - sets all axes limits and labels&quot;&quot;&quot;

    def __ax3d_funcs(ax3d, suffix):
        return [getattr(ax3d, f&quot;{AX3D_PREFIX_SETTER}{axis}{suffix}&quot;) for axis in AX3D_AXES]

    for limit_setter in __ax3d_funcs(ax, AX3D_LIM_SUFFIX):
        limit_setter(*PIXEL_VALUE_LIMITS)

    for label_setter, label in zip(__ax3d_funcs(ax, AX3D_LABEL_SUFFIX), labels):
        label_setter(label)


def scatter_3d_from_image(image, labels, fig, plt_axis, num_columns, num_rows, column, row):
    channel_values_in_rows = to_channel_values_in_rows(image)

    plt_axis.axis(&apos;off&apos;)
    ax = fig.add_subplot(num_rows, num_columns, (row * num_columns) + column + 1, projection=&quot;3d&quot;)
    set_up_3d_plot_limits_and_labels(ax, labels)

    ax.scatter(*channel_values_in_rows)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;And our invocation, for example, for the 2nd row, and the 3rd image, might look like this:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;fig, axs = plt.subplots(3, 10)

row = 1
image_index = 2

# Some code in between...

scatter_3d_from_image(frame, [&quot;r&quot;, &quot;g&quot;, &quot;b&quot;], fig, axs[1, 2], 10, 3, 2, 1)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;admonitionblock note&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td class=&quot;icon&quot;&gt;
&lt;i class=&quot;fa icon-note&quot; title=&quot;Note&quot;&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class=&quot;content&quot;&gt;
The code for the current visualization appears convoluted. That&amp;#8217;s because &lt;a href=&quot;https://matplotlib.org/stable/gallery/mplot3d/mixed_subplots.html&quot;&gt;we&amp;#8217;re mixing up 2D and 3D diagrams in one plot&lt;/a&gt;. The 3D elements force us to use the &lt;code&gt;fig.add_subplot&lt;/code&gt; API, whereas our 2D elements are reliant on the &lt;code&gt;plt.subplots&lt;/code&gt; API, with array of 2D axes. This is why we also include the &lt;code&gt;plt_axis.axis(&apos;off&apos;)&lt;/code&gt; call – we need to &quot;hide&quot; the axes of the 2D diagram &quot;slot&quot; we&amp;#8217;ve created with &lt;code&gt;plt.subplots&lt;/code&gt;.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Right, let&amp;#8217;s see our results:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/selector_corner_scatter_plots.png&quot; alt=&quot;selector corner scatter plots&quot; width=&quot;2880&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 7. Scatter plots for target designator &quot;corner fragment&quot;. First row is the 28px*3px image, scaled up, second is the RGB scatter plot, third is HLS scatter plot.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/selector_full_scatter_plots.png&quot; alt=&quot;selector full scatter plots&quot; width=&quot;2880&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 8. Scatter plots for complete target designators. First row is the 96px*96px image, scaled up, second is the RGB scatter plot, third is HLS scatter plot.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;For the fragment version, the clustering across the two different colorspaces is pretty similar — arguably, the HLS one looks more &quot;compact&quot;, but that might be misleading. The full designator versions offer a starker difference between RGB and HLS. In most cases, in the HLS plot, we can make out the same kind of cluster as in the fragment diagrams, whereas the RGB versions are much more of a chaotic jumble.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;However, it is high time we started to act like true MechWarriors – in this case, stop relying on the MK. I Eyeball, and turn to cold and calculating machine systems for our target acquisition. This means, in our case, clustering.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;What we&amp;#8217;ll do now is join both imagesets into large images, and run a clustering algorithm on them. This will &quot;smoothen out&quot; differences across our samples and hopefully make the cluster we&amp;#8217;re interested in - the target designator color cluster – more pronounced, and thus easier to pick out by the algo.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Speaking of algos, scikit-learn has &lt;a href=&quot;https://scikit-learn.org/stable/modules/clustering.html#overview-of-clustering-methods&quot;&gt;a very convenient overview of the clustering algorithms it offers&lt;/a&gt;. Now, we need to consider our requirements and preferences.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As far as requirements are concerned, anything with &quot;even cluster sizes&quot; is right out. Not only it&amp;#8217;s unlikely to be the case just looking at the variability of colorspace values across our images, but also we&amp;#8217;re really interested in the one (postulated) cluster that will represent the target designator colorspace values. So K-Means, Spectral clustering and Bisecting K-Means should be excluded.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;For scaling, we don&amp;#8217;t really about it – our data space size is too small for it matter significantly.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We now have a choice of two broad categories of algos, split on the kind of main parameter, that being:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;distance threshold: this is&amp;#8230;&amp;#8203; kind of what we want to obtain from the clustering. We can have a good guess, but let&amp;#8217;s maybe leave algos requiring in case something doesn&amp;#8217;t work.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;neighborhood size: also arguably something that we want to get from the clustering, but in this case we can at least estimate some minima from the size of the box elements. So, let&amp;#8217;s go with that first.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This leaves us with Affinity propagation, DBSCAN, OPTICS, Gaussian mixtures, and BIRCH. We can start with any one of them. However, DBSCAN looks like the best candidate so far. That&amp;#8217;s because of a quirk in its parametrization. To quote &lt;a href=&quot;https://scikit-learn.org/stable/modules/clustering.html#dbscan&quot;&gt;the docs&lt;/a&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;[&amp;#8230;&amp;#8203;] the parameter eps is crucial to choose appropriately for the data set and distance function and usually cannot be left at the default value. It controls the local neighborhood of the points. When chosen too small, most data will not be clustered at all (and labeled as -1 for “noise”).&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This low amount of clustering and labeling most data as &quot;noise&quot; is, in our case, exactly what we want!&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Let&amp;#8217;s get to work then. First, let&amp;#8217;s merge all our images into one – we can do this with a NumPy array-level operation:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;# all images
designator_images: list[np.array] = ...

# all with just the designator segments
designator_segment_images = [img[8:37, 7:10, :] for img in designator_images]

designator_image_concat = np.concatenate(designator_images, axis=1)

designator_segment_images_concat = np.concatenate(designator_segment_images, axis=1)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This will produce the following two images:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/designator_concat.png&quot; alt=&quot;designator concat&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/designator_segment_concat.png&quot; alt=&quot;designator segment concat&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Let&amp;#8217;s start with the latter first, as the result obtained from that will help us find the &quot;right&quot; cluster of interest in the former. In fact, because this image contains just (a portion) of the designator, we want the clustering algorithm to generate exactly 1 cluster, and leave the rest as noise.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Alright, so &lt;a href=&quot;https://scikit-learn.org/stable/modules/generated/sklearn.cluster.DBSCAN.html&quot;&gt;DBSCAN&lt;/a&gt; has several parameters, of which two are of particular interest: &lt;code&gt;eps&lt;/code&gt; and &lt;code&gt;min_samples&lt;/code&gt;. Both determine how the neighborhood of &quot;core&quot; points is defined – an important distinction, by the way, between that and the size of the entire cluster, which DBSCAN does not concern itself with directly.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Keeping this sizing caveat in mind, we&amp;#8217;ll ballpark both parameters.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;For &lt;code&gt;eps&lt;/code&gt;, we want a decent, but not to overly broad of a distance, so that the cluster doesn&amp;#8217;t capture too many points. A good value would be to at least allow a distance of &lt;code&gt;2&lt;/code&gt; in any direction (H, S, L). Since we&amp;#8217;re keeping the default Euclidean metric, this gives us &lt;code&gt;eps=2**3=8&lt;/code&gt;. Of course, this also lets through points that are 8 values away along any one axis, but this won&amp;#8217;t be a problem here.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Now, &lt;code&gt;min_samples&lt;/code&gt;. One would be tempted to get a large number, like the size of a single of one of the 3 &quot;bars&quot; each designator segment has (recall the individual images are of shape &lt;code&gt;(3, 28)&lt;/code&gt; in this case). This would give us &lt;code&gt;28*10=280&lt;/code&gt; (10 being the image count). Great! Except it won&amp;#8217;t work – no cluster will be recognized. No &quot;core&quot; point can apparently be found for the given &lt;code&gt;eps&lt;/code&gt; value in this case. However, half of that, i.e. &lt;code&gt;140&lt;/code&gt; works, so let go with that.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Our clustering result is generated through the following code:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;from sklearn.cluster import DBSCAN

def prepare_image_for_clustering(image):
    # since we&apos;ve determined HSL/HLS to likely be superior for clustering
    hsl = cv.cvtColor(image, cv.COLOR_RGB2HLS)

    # DBSCAN, as typical of sklearn Estimators, needs a columnar format of the values
    in_rows = to_channel_values_in_rows(hsl)
    return np.transpose(in_rows)


def hsl_clusters_of(X, eps, min_samples):
    # we&apos;re explicitly adding that we want a l2/Euclidean metric
    dbscan = DBSCAN(eps=eps, min_samples=min_samples, p=2)

    # our clustering fit
    return dbscan.fit_predict(X)


X_segment = prepare_image_for_clustering(designator_segment_images_concat)

clusters = hsl_clusters_of(X_segment, eps=2 ** 3, min_samples=(28 * 10) // 2)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;clusters&lt;/code&gt; is simply a 1-D denoting which cluster a given sample belongs to. We&amp;#8217;ll fit it into a dataframe to allow for analysis and display&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_1&quot; class=&quot;footnote&quot; href=&quot;#_footnote_1&quot; title=&quot;View footnote.&quot;&gt;1&lt;/a&gt;]&lt;/sup&gt; :&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;COLORSPACE = (&quot;H&quot;, &quot;L&quot;, &quot;S&quot;)


def cluster_to_df(image_data_h_l_s, clusters):
    return pd.DataFrame(data=zip(image_data_h_l_s[:, 0], image_data_h_l_s[:, 1], image_data_h_l_s[:, 2], clusters), columns=list(COLORSPACE)+[&quot;cluster_id&quot;])


clustering_df = cluster_to_df(X_segment, clusters)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;and start verifying that we got our desired result:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;clustering_df[&quot;cluster_id&quot;].value_counts()

# prints out:
#  -1    480
#  0    390
# Name: cluster_id, dtype: int64&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Yup, we&amp;#8217;ve got our single cluster (since &lt;code&gt;-1&lt;/code&gt; is the special &quot;noise&quot; value). Now for the cluster&amp;#8217;s summary statistics:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;target_values = clustering_df[clustering_df[&quot;cluster_id&quot;] == 0]

target_values.describe()

# prints out:
#                 H           L           S  cluster_id
# count  390.000000  390.000000  390.000000       390.0
# mean   172.815385  101.858974  254.423077         0.0
# std      1.672942    8.157392    1.863733         0.0
# min    170.000000   83.000000  243.000000         0.0
# 25%    172.000000   96.000000  255.000000         0.0
# 50%    173.000000  102.000000  255.000000         0.0
# 75%    174.000000  109.000000  255.000000         0.0
# max    179.000000  121.000000  255.000000         0.0&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The mean hue value is close to what we estimated earlier from the histograms.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Now, let&amp;#8217;s try to run the clustering on the concatenated full designator images. We&amp;#8217;re going to multiply &lt;code&gt;min_samples&lt;/code&gt; by 4, as every image has that number of previously extracted segments.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;X_designators = prepare_image_for_clustering(designator_image_concat)

clusters_full = hsl_clusters_of(X_designators, eps=2 ** 3, min_samples=((28 * 10) // 2)*4)

clustering_df_full = cluster_to_df(X_designators, clusters_full)

clustering_df_full[&quot;cluster_id&quot;].value_counts()

# prints out:
# -1     61136
#  1      7300
#  2      4137
#  5      3869
#  7      3185
#  4      2553
#  3      2082
#  9      1989
#  6      1886
#  0      1795
#  8      1597
#  10      631&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Lots more clusters, but that&amp;#8217;s to be expected. The first cluster is hopefully want we want, followed by, most likely, greyscale values in some of the test images. Not leaving anything to chance, let&amp;#8217;s check out what hue values are represented by each cluster.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We&amp;#8217;re going to calculate three &lt;a href=&quot;https://en.wikipedia.org/wiki/Percentile&quot;&gt;percentiles&lt;/a&gt; of the hue values for each cluster: the 10th, the 50th (i.e., median), and the 90th. This is a serviceable exploration heuristic if a quick check is desired, and a relatively varied distribution is suspected. We&amp;#8217;re also including the cluster size again, for good measure.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;from functools import partial


# The keywords of agg are completely arbitrary - they&apos;re just
# our column names in the output. What is important is that we provide
# a function object (Callable) for each value. That is why we need to
# invoke partial for the percentiles.
clustering_df_full.groupby(by=&quot;cluster_id&quot;)[&quot;H&quot;].agg(centile_10=partial(np.percentile, q=10),
                                                     median=np.median,
                                                     centile_90=partial(np.percentile, q=90),
                                                     count=len)


# prints out:
#             centile_10  median  centile_90  count
# cluster_id
# -1                 7.0    30.0       173.0  61136
#  0                 0.0     0.0         0.0   1795
#  1               171.0   173.0       177.0   7300
#  2                 0.0     0.0         0.0   4137
#  3                 0.0     5.0        13.0   2082
#  4                30.0    30.0        30.0   2553
#  5                15.0    26.0        34.0   3869
#  6                 7.0    16.0        20.0   1886
#  7                27.0    31.0        33.0   3185
#  8               165.0   165.0       168.0   1597
#  9                 0.0     0.0         0.0   1989
#  10                0.0     0.0         0.0    631&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;(by the way: yes, we could have just used &lt;a href=&quot;https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.describe.html&quot;&gt;&lt;code&gt;describe&lt;/code&gt;&lt;/a&gt; here – the only benefit is a slightly more focused output. Don&amp;#8217;t worry, we&amp;#8217;ll come back to that method later on.)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Phew, looks like we do have most of the relevant values in one cluster (&lt;code&gt;cluster_id == 1&lt;/code&gt;). The guess that the other of the largest clusters represent greyscale values was also correct (if you&amp;#8217;re wondering why there are multiple ones with &lt;code&gt;H&lt;/code&gt; values &lt;code&gt;0&lt;/code&gt; – that&amp;#8217;s because they almost certainly differ in the other colorspace components).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The only mildly worrying thing is cluster &lt;code&gt;8&lt;/code&gt;, being very close in hue to the red of our designator. We&amp;#8217;ll keep that in mind as we progress into the next steps.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Before we close this section, it would serve us to actually visualize the cluster spaces. We&amp;#8217;re going to do it in two ways – one, by using a predefined colormap, the other, by using the actual (median) colors of the cluster. The code for the diagram generation is as follows (warning – lots of matplotlib idiosyncrasies we won&amp;#8217;t go into detail here):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;import matplotlib.patches as mpatches

MISSING_CLUSTER_ID = -1
MISSING_CLUSTER_COLOR = [0.75, 0, 0.75]

LEGEND_VALUES_PER_COL_MAX = 2


def show_legend_no_alpha(ax, colors, labels, title):
    &quot;&quot;&quot;Helper function - displays a legend for the cluster colors with 0 transparency&quot;&quot;&quot;
    # removing the alpha channel (RGBA -&amp;gt; RGB)
    handles_colors = [c[:3] for c in colors]

    # following
    # https://matplotlib.org/stable/tutorials/intermediate/legend_guide.html#creating-artists-specifically-for-adding-to-the-legend-aka-proxy-artists
    final_handles = [mpatches.Patch(color=color, label=label) for (color, label) in zip(handles_colors, labels)]

    # and ensure that the cluster values are the labels...
    ax.legend(handles=final_handles, ncols=(len(final_handles) // LEGEND_VALUES_PER_COL_MAX) + 1, loc=&quot;upper right&quot;, title=title)


def draw_clusters_cm(cluster_data: pd.DataFrame, alpha: float = 1., colormap=&quot;tab20b&quot;):
    &quot;&quot;&quot;Primary presentation function – draws provided clusters with a predefined colormap
    See https://matplotlib.org/stable/tutorials/colors/colormaps.html
    &quot;&quot;&quot;
    fig = plt.figure()
    ax = fig.add_subplot(projection=&apos;3d&apos;)

    set_up_3d_plot_limits_and_labels(ax, COLORSPACE)

    scatter_plot = ax.scatter(*[cluster_data[comp] for comp in COLORSPACE],
                              c=cluster_data[&quot;cluster_id&quot;],
                              alpha=alpha,
                              cmap=colormap)

    handles, labels = scatter_plot.legend_elements()
    show_legend_no_alpha(ax, [c.get_color() for c in handles], labels, &quot;cluster_id&quot;)

    plt.show()


def draw_clusters_real_color(cluster_data: pd.DataFrame, alpha: float = 1.):
    &quot;&quot;&quot;Alternative presentation function – draws provided clusters with &quot;real&quot; colors,
     i.e. each of the median HLS values for the cluster.&quot;&quot;&quot;
    fig = plt.figure()
    ax = fig.add_subplot(projection=&apos;3d&apos;)

    set_up_3d_plot_limits_and_labels(ax, COLORSPACE)

    # create a color dict for the cluster colors
    median_cluster_colors = cluster_data.groupby(&quot;cluster_id&quot;)[list(COLORSPACE)].median()

    median_cluster_color_rgb = cv.cvtColor(np.array([median_cluster_colors.values]).astype(&apos;uint8&apos;), cv.COLOR_HLS2RGB)

    colors_per_cluster = dict(
        list(zip(median_cluster_colors.index,
                 (median_cluster_color_rgb[0] / float(PIXEL_VALUE_LIMITS[1])).tolist())))

    # let&apos;s set -1 to an unusual color, but background color (such as light magenta) for good measure
    if MISSING_CLUSTER_ID in colors_per_cluster:
        colors_per_cluster[MISSING_CLUSTER_ID] = MISSING_CLUSTER_COLOR

    # we&apos;re doing things differently now - drawing individual scatter plots per cluster
    for cluster_id, color in colors_per_cluster.items():
        cluster_data_specific = cluster_data[cluster_data[&quot;cluster_id&quot;] == cluster_id]
        ax.scatter(*[cluster_data_specific[comp] for comp in COLORSPACE],
                   color=color,
                   label=cluster_id,
                   alpha=alpha,
                   )

    handles, labels = ax.get_legend_handles_labels()
    show_legend_no_alpha(ax, [h.get_edgecolor()[0] for h in handles], labels, &quot;cluster_id&quot;)

    plt.show()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;and these are the diagrams we want to generate:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;draw_clusters_real_color(clustering_df[clustering_df[&quot;cluster_id&quot;] == 0])

draw_clusters_cm(clustering_df_full[clustering_df_full[&quot;cluster_id&quot;] != -1])
draw_clusters_real_color(clustering_df_full[clustering_df_full[&quot;cluster_id&quot;] != -1])
draw_clusters_real_color(clustering_df_full, 0.01)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;which gives us the following:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/cluster_diags.png&quot; alt=&quot;cluster diags&quot; width=&quot;1920&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 9. Scatter plots for (going left to right): 1) The sole cluster in the segment image. 2) Clusters in the full image, shown with their median colors. 3) Clusters in the full image, shown with a predefined colormap/palette. 4) All pixels in the full image, including non-clustered ones.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Couple of notes here:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;we were needlessly concerned about cluster &lt;code&gt;8&lt;/code&gt; being, perhaps, cluster &lt;code&gt;1&lt;/code&gt; &apos;s &quot;lost twin&quot; – from the diagram, especially the median color diagram, it is apparent that this cluster is actually very far away in our colorspace. Just shows the importance of properly presenting data and information extracted therefrom, and not jumping into conclusion based on solely a partial view of the data.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;speaking of visualisation:&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;compare diagram 2 with diagram 3 – while the &quot;true color&quot; diagram provides some immediate information as to what the clusters represent, only the &quot;palette&quot; really demonstrates cluster boundaries.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;for the final diagram, the alpha of all points is reduced greatly. This is because the no-cluster classification dominates within our dataset, so using no alpha would render our diagram pretty much unreadable.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;And now, for the &lt;em&gt;pièce de résistance&lt;/em&gt; – color information for cluster &lt;code&gt;1&lt;/code&gt; in the full image!&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Or, well, it would be, but we need to take of one thing first. Some readers have probably noticed the way we calculated the median color values of the cluster, i.e., get the median of each individual HSL component. That grouping of values is actually a &lt;a href=&quot;https://en.wikipedia.org/wiki/Median#Marginal_median&quot;&gt;marginal median&lt;/a&gt;. It is not the sole representation of a median in multidimensional spaces. In fact, depending on your dataset and on the relations between the components, it may be completely non-representative of the examined data, as discussed in &lt;a href=&quot;https://stats.stackexchange.com/a/459295&quot;&gt;this answer&lt;/a&gt; on Cross Validated.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;At the surface level, the Cluster Of Interest looks like your typical unassuming, convex blob with likely straightforward relationships between the components. This may be deceiving, as, just by looking at the diagram, we cannot really see what the actual value densities are &lt;em&gt;within&lt;/em&gt; that cluster. So, let&amp;#8217;s go one step further and make sure we get our values right.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Another, somewhat more generally robust median in a multidimensional space is the &lt;a href=&quot;https://en.wikipedia.org/wiki/Geometric_median&quot;&gt;geometric median&lt;/a&gt;&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_2&quot; class=&quot;footnote&quot; href=&quot;#_footnote_2&quot; title=&quot;View footnote.&quot;&gt;2&lt;/a&gt;]&lt;/sup&gt;. It&amp;#8217;s not available out-of-the-box in Pandas or numpy – but there &lt;a href=&quot;https://pypi.org/project/geom-median/&quot;&gt;is a third-party library that provides&lt;/a&gt; it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;To calculate it, we first need to extract the combined HLS values into a dedicated column:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;# filtering out just the cluster of interest
cluster_1_data = clustering_df_full[clustering_df_full[&quot;cluster_id&quot;] == 1].drop(columns=[&apos;cluster_id&apos;])


# extracting the combined values
cluster_1_data[&quot;HLS&quot;] = [r for r in cluster_1_data[[&quot;H&quot;, &quot;L&quot;, &quot;S&quot;]].values]

# normalizing them to [0,1], as needed for the median calculation
# doing so in a separate step means we have access to the vectorized operation syntax
cluster_1_data[&quot;HLS_norm&quot;] = cluster_1_data[&quot;HLS&quot;]/COMPONENT_VALUE_MAX

# keeping just the normalized combined column
cluster_1_data = cluster_1_data.drop(columns=[&quot;HLS&quot;])&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Now we can actually obtain the geometric median:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;from geom_median.numpy import compute_geometric_median

def compute_geom_median_on_series(series: pd.Series):
    result = compute_geometric_median(series.values)
    # &quot;denormalize&quot; the result back to [0, 255]
    return result.median*COMPONENT_VALUE_MAX

geometric_median = cluster_1_data[&quot;HLS_norm&quot;].agg(compute_geom_median_on_series)

geometric_median

# prints out
# array([173.03971593, 100.6201285 , 254.43690908])&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Now to demonstrate it, using the color swatch function we&amp;#8217;ve defined previously&amp;#8230;&amp;#8203;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;# &quot;unwinding&quot; the result array into positional arguments
# important to keep the sequence right, which in this case
# it is (H,L,S)
display_color_swatch(*geometric_median)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/selector_geom_median.png&quot; alt=&quot;selector geom median&quot; width=&quot;40&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&amp;#8230;&amp;#8203;and compare to the individual medians:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;color_components = [&quot;H&quot;, &quot;L&quot;, &quot;S&quot;]
cluster_1_data[color_components].agg(np.median)

# prints out
# H    173.0
# L    101.0
# S    255.0
# dtype: float64&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We can see that, while in this case, the result is almost identical, there is still a small difference (in the saturation).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;For good measure, let&amp;#8217;s also generate more summary statistics for the color components, this time using &lt;code&gt;describe&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;color_components = [&quot;H&quot;, &quot;L&quot;, &quot;S&quot;]
cluster_1_stats = cluster_1_data[color_components].describe(percentiles=[0.1, 0.5, 0.9]).round(2)

cluster_1_stats

# prints out
#              H        L        S
# count  7300.00  7300.00  7300.00
# mean    173.59    96.76   253.86
# std       2.39    15.72     3.06
# min     168.00    55.00   240.00
# 10%     171.00    72.00   250.00
# 50%     173.00   101.00   255.00
# 90%     177.00   114.00   255.00
# max     180.00   127.00   255.00&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Finally, let&amp;#8217;s summarize our results into a single &lt;code&gt;DataFrame&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;cluster_1_stats_final = cluster_1_stats.copy()

cluster_1_stats_final.loc[&quot;geom_median&quot;] = geometric_median

cluster_1_stats_final = cluster_1_stats_final.drop(index=[&quot;count&quot;]).round(2)

cluster_1_stats_final

# prints out
#                   H       L       S
# mean         173.59   96.76  253.86
# std            2.39   15.72    3.06
# min          168.00   55.00  240.00
# 10%          171.00   72.00  250.00
# 50%          173.00  101.00  255.00
# 90%          177.00  114.00  255.00
# max          180.00  127.00  255.00
# geom_median  173.04  100.62  254.44&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The numerical values we&amp;#8217;ve now obtained will help us gauge the extent of the filtering criteria, as we move on to actually extracting the designators themselves.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Now, was this much work necessary to determine the colors that interest us? The answer is &quot;absolutely not&quot;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We could&amp;#8217;ve just put the example images into an image editor and let it sample the color. Then, we might&amp;#8217;ve eyeballed the color component intervals (i.e., min, max, and so on) for the purpose of creating a prototype extractor of the designator images. This is, in fact, the level of effort that should usually be applied when making prototypes.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Hell, we could&amp;#8217;ve gone with an alternative route (if allowed by relevant copyright law and additional agreements such as the EULA) – look into the game&amp;#8217;s graphic assets, if available, and find the elements from which the designator is constructed. This would allow us to estimate the color range from stuff like the designator&amp;#8217;s alpha channel&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_3&quot; class=&quot;footnote&quot; href=&quot;#_footnote_3&quot; title=&quot;View footnote.&quot;&gt;3&lt;/a&gt;]&lt;/sup&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Moreover, our data size is suspiciously small. In a project of similar magnitude, we should have hundreds, if not thousands of samples to use for the clustering. The reliability of the result rests pretty much on the author&amp;#8217;s confidence to handpick representative samples consistently.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The intent of this post, however, was twofold.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;First, to demonstrate how to go about solving this kind of problem somewhat more rigorously, so that we can have a little bit more confidence in what we base our further work on.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The other was showcasing a number of data analysis tools and the way to use them, allowing them to be used when tackling similar data extraction tasks.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Well, here we are. In the next couple of entries, we&amp;#8217;ll proceed with the image extraction itself, using several different techniques of varying complexity, power, and performance. To provide a bit of a teaser: in the immediately subsequent post, we&amp;#8217;ll start with a couple of simple and current techniques, including the usage of some libraries that are definitely more modern than OpenCV. Watch this space!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_1&quot;&gt;
&lt;a href=&quot;#_footnoteref_1&quot;&gt;1&lt;/a&gt;. If you need a general refresher on what Pandas DataFrames are, have a look at this &lt;a href=&quot;https://medium.com/softwaremill-tech/exploring-a-dataset-episode-one-choosing-your-adventure-767e1c8fb796&quot;&gt;blog post&lt;/a&gt;.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_2&quot;&gt;
&lt;a href=&quot;#_footnoteref_2&quot;&gt;2&lt;/a&gt;. &lt;strong&gt;Do not&lt;/strong&gt; confuse it with the &lt;a href=&quot;https://en.wikipedia.org/wiki/Geometric_mean&quot;&gt;geometric mean&lt;/a&gt;. The geometric median deals directly with spatial relationships of the values, whereas the &quot;geometric&quot; in the geometric mean refers to the &lt;em&gt;distribution&lt;/em&gt; of values in some set of real numbers.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_3&quot;&gt;
&lt;a href=&quot;#_footnoteref_3&quot;&gt;3&lt;/a&gt;. If it&amp;#8217;s not set programmatically, that is.
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Extracting Machine Learning Training Data from Video Games – Problem Statement</title>
      <link>https://xn--mikoak-6db.net/blog/2024/blog-extracting-data-1-intro.html</link>
      <pubDate>Sun, 5 May 2024 00:00:00 +0200</pubDate>
      <guid isPermaLink="false">blog/2024/blog-extracting-data-1-intro.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/intro_splash.png&quot; alt=&quot;intro splash&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 1. Source: SD 2.1+own work.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Spring has sprung in full force in the Northern hemisphere, so it&amp;#8217;s time to dust off the old &quot;submit publication&quot; button! In this entry, we&amp;#8217;ll look into ways of extracting training data from computer-generated data sources without breaking too much of a sweat. Specifically, we&amp;#8217;ll introduce the data source we&amp;#8217;ll work on, and define a problem statement. This will form a basis for later blog entries that will tackle various aspects of said problem. Read on!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;One of the interesting aspects of our times is the increasing importance of data derived from &quot;human-made&quot; sources. Early automation was almost always primarily grounded in the physical reality. As time went by, systems that built upon data delivered by &lt;em&gt;other&lt;/em&gt; systems become more worthwhile: analysis of social media trends, electronic sales projections, and so on. And, well, there&amp;#8217;s also lots of stuff built on top of data originating from video games. Anti-cheat systems, usage analytics – used both for objectively valuable insights like improving accessibility, or less so, like &quot;optimizing&quot; microtransactions – with the normalization of gaming as a pastime for all ages, there&amp;#8217;s a gold mine of opportunity here. And we&amp;#8217;ll take some advantage of it; but first, a bit more about our subject.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The game in question is MechWarrior Online, subsequently referred to as MWO. As the name suggests, it is a multiplayer-only fair set in the &lt;a href=&quot;https://www.sarna.net/wiki/BattleTech_Universe&quot;&gt;Battletech/MechWarrior&lt;/a&gt; universe. It offers several game modes, virtually all of them centered around two teams fighting each other to destruction, while also trying to balance achieving auxiliary objectives.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/intro_pgi_promo_screenshot_1.png&quot; alt=&quot;intro pgi promo screenshot 1&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 2. Promotional screenshot of MechWarrior Online. An &lt;a href=&quot;https://www.sarna.net/wiki/Ebon_Jaguar_(Cauldron-Born)&quot;&gt;Ebon Jaguar heavy mech&lt;/a&gt; engaging a target (possibly an armless &lt;a href=&quot;https://www.sarna.net/wiki/Bushwacker&quot;&gt;Bushwacker&lt;/a&gt;) with support weapons. Source: &lt;a href=&quot;https://piranhagames.com/games&quot;&gt;Piranha Games&lt;/a&gt;.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Everyone pilots a Battlemech (no &lt;a href=&quot;https://en.wiktionary.org/w/index.php?title=combined_arms&amp;amp;oldid=77819437&quot;&gt;combined arms&lt;/a&gt; here) : a customizable, heavily armed, ludicrously armored, single-seat, bipedal&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_1&quot; class=&quot;footnote&quot; href=&quot;#_footnote_1&quot; title=&quot;View footnote.&quot;&gt;1&lt;/a&gt;]&lt;/sup&gt; combat vehicle. While it sounds like a First-Person Shooter with some sci-fi bling thrown on, that couldn&amp;#8217;t be further from the truth. Mechs have multiple, independently destroyable components, each potentially housing multiple armaments and pieces of equipment. Players need to manage ammunition, heat generated by own (or hostile&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_2&quot; class=&quot;footnote&quot; href=&quot;#_footnote_2&quot; title=&quot;View footnote.&quot;&gt;2&lt;/a&gt;]&lt;/sup&gt;) weapon fire,  damage distribution, terrain use, team coordination, and so on.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The general vibe feels less like a squad-based FPS, and more like somewhere between naval combat and simultaneously controlling an armored platoon. Mad aiming skillz are much less important here than situational awareness, forethought, planning, and team coordination. Not only that, but the mechs themselves are – as previously stated – highly customizable, to the point that a significant amount of player&amp;#8217;s time is spent tweaking and trying out different weapon/equipment configurations. All this coalesces into a unique experience, and so a unique data landscape to work on.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;possible_premises_and_their_choice&quot;&gt;Possible premises and their choice&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Having explained the circumstances we have on our hands, let&amp;#8217;s see what we can do with the source material. Since many players occasionally record their games for later analysis (and some for streaming), the original idea was to create a &quot;virtual coach&quot; that would, based on said recordings, call out possible improvements in the player&amp;#8217;s style.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Positioning (w.r.t. friendlies and the likely enemy placement) is probably the most important learnable skill in MWO - but getting full data for that (known friendly and enemy positions at a given point of time) is difficult to extract just from existing footage. Not only that, developing the model itself would be decidedly non-trivial. In other words – assisting positioning in MWO is an intriguing challenge, but complex enough to warrant a whole separate series.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;So – at least for this blog series – let&amp;#8217;s try something more manageable: &lt;a href=&quot;https://en.wikipedia.org/w/index.php?title=Situation_awareness&amp;amp;oldid=1213487970&quot;&gt;situational awareness&lt;/a&gt;. With all the aspects of managing a mech occupying the player brain&amp;#8217;s processing power, slipping up and being completely oblivious to a hostile mech&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_3&quot; class=&quot;footnote&quot; href=&quot;#_footnote_3&quot; title=&quot;View footnote.&quot;&gt;3&lt;/a&gt;]&lt;/sup&gt; running through the field of vision  is surprisingly common. However, such mistakes can usually prove fatal, as said overlooked opponent can easily get behind the player&amp;#8217;s mech and start tearing them apart. Moreover, the initial situation often constitutes &lt;em&gt;the opponent&amp;#8217;s&lt;/em&gt; error, and would be prime time to engage.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Having contextualized our circumstances, we now need a problem statement with specific requirements and goal conditions. Here it is:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;admonitionblock important&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td class=&quot;icon&quot;&gt;
&lt;i class=&quot;fa icon-important&quot; title=&quot;Important&quot;&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class=&quot;content&quot;&gt;
Develop a model that detects enemies in-view that could have been targeted, but weren&amp;#8217;t, and mark them on the footage. Bonus goal: mark situations where the player was not actively engaging &lt;em&gt;any&lt;/em&gt; target, but could.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;OK, looks good and &lt;em&gt;relatively&lt;/em&gt; manageable. From our problem statement, we can work out that we need some way of determining:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;the positions of mechs in a given frame of the footage;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;that a given mech is friendly or hostile;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;that a given hostile mech is &quot;non-targeted&quot;;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;that the player is in a &quot;non-engaging&quot; state.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We can also declare a couple of non-functional requirements:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;our solution does not have to be especially performant – we&amp;#8217;re operating on pre-recorded footage;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;our solution should weigh recall over precision — we&amp;#8217;re fine if e.g., a friendly mech is falsely marked as a &quot;non-targeted&quot; hostile mech, as it should be apparent from the context of the footage, and the recommendation simply discarded.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Now, we&amp;#8217;ll take a brief look at the game&amp;#8217;s UI to determine what, exactly, we want to train.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;identifying_screen_ui_elements_relevant_for_machine_learning_model_training&quot;&gt;Identifying screen UI elements relevant for machine learning model training&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Let&amp;#8217;s examine some screenshots, enhanced with context markings. Examples are demonstrated in the slideshow below:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;videoblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;video src=&quot;../../illustrations//mwo_training_data_extraction/img/interface_demo.mp4&quot; width=&quot;1024&quot; autoplay controls loop&gt;
Your browser does not support the video tag.
&lt;/video&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;Targeting &lt;a href=&quot;https://en.wikipedia.org/w/index.php?title=Reticle&amp;amp;oldid=1214730405&quot;&gt;reticles&lt;/a&gt;: yes, the mechs have multiples thereof, for different types of weapons, and their &lt;a href=&quot;https://en.wiktionary.org/w/index.php?title=hardpoint&amp;amp;oldid=77479279#Noun&quot;&gt;hardpoint&lt;/a&gt; locations.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Unmarked hostile mech.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Target designator on a marked, hostile mech: the player can mark at most one hostile mech at a time.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Targeted mech&amp;#8217;s scan result, showing weapon and component status&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_4&quot; class=&quot;footnote&quot; href=&quot;#_footnote_4&quot; title=&quot;View footnote.&quot;&gt;4&lt;/a&gt;]&lt;/sup&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This is just some information relevant to the player on the screen, but pretty much all that we need for our purposes.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In end effect, we have two types of data to extract from the video&amp;#8217;s frames:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;UI-derived information, such as weapon firing state,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;detectable objects.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;While the former is extractable using simple image manipulation and &quot;classic&quot; computer vision techniques, this is not so with detectable objects, i.e. the mechs. For that, we need to train some sort of object detection model.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We could go over each recording and meticulously mark each and every mech. But who has the time (or money to hire annotators) for that?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We might consider &quot;traditional&quot; motion detection techniques, used widely in consumer IP cameras (and explained in a myriad of online tutorials), but that option also falls flat. Why? Because &lt;strong&gt;both&lt;/strong&gt; the objects &lt;strong&gt;and&lt;/strong&gt; the camera are moving – sometimes quite vigorously. So that&amp;#8217;s one possible free lunch out of reach. We will, however, consider the potential to exploit research into movement detection on mobile cameras, but that&amp;#8217;ll come later on.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Now, take another look at the screenshot: see how the hostile mech is nicely marked&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_5&quot; class=&quot;footnote&quot; href=&quot;#_footnote_5&quot; title=&quot;View footnote.&quot;&gt;5&lt;/a&gt;]&lt;/sup&gt;? And how about that nice bracketing of the actively targeted mech? Almost like a bounding box, right?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Well, it looks like we have a way out – we&amp;#8217;ll try to automatically extract detection boxes by annotating targeted hostile mechs as objects to be detected. We can use &lt;em&gt;that&lt;/em&gt; data as inputs for subsequent training of our &quot;primary&quot; detection models.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//mwo_training_data_extraction/img/intro_screenshot_processed.png&quot; alt=&quot;intro screenshot processed&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 3. For completeness, a screenshot that&amp;#8217;s more representative of situations in the game – an evidently more complex scene. The view is unzoomed, meaning the full interface, including the quasi-diegetic cockpit displays, is visible. The player, and two teammates, are engaging an (unlucky) hostile mech that just rounded a corner. The opponent&amp;#8217;s readout is showing damage to the torso components, including actively receiving fire to the central portion. PPI has been edited out.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In this entry:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;we&amp;#8217;ve introduced the use case we&amp;#8217;re going to handle in the blog series initiated by this entry: extracting training data from video games, and putting it to use.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We&amp;#8217;ve chosen MechWarrior Online (MWO) as our exemplary data source.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We&amp;#8217;ve also examined the automation problem landscape in MWO:&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;we considered several potentials, such as assisting in player positioning,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;but, for the immediate future, settled on a more manageable problem: helping with situational awareness.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Finally, we also identified the screen elements that are relevant for our model training.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In the upcoming several follow-up entries of the series, we&amp;#8217;ll explore how to obtain training data for the defined task, by way of identifying and extracting the relevant UI elements. We&amp;#8217;re going to use, and eventually compare, several different methods to accomplish this task. And yes, that means we&amp;#8217;ll actually start writing some code. Stay tuned!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_1&quot;&gt;
&lt;a href=&quot;#_footnoteref_1&quot;&gt;1&lt;/a&gt;. Battletech fans will be quick to note that the last two points aren&amp;#8217;t always the case in the setting, but it &lt;strong&gt;is&lt;/strong&gt; in MWO, so you can safely ignore them (unless they&amp;#8217;re in your rear arc).
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_2&quot;&gt;
&lt;a href=&quot;#_footnoteref_2&quot;&gt;2&lt;/a&gt;. &amp;#8230;&amp;#8203;or sometimes friendly&amp;#8230;&amp;#8203;
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_3&quot;&gt;
&lt;a href=&quot;#_footnoteref_3&quot;&gt;3&lt;/a&gt;. Especially a small and fast one that is equipped for stealth.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_4&quot;&gt;
&lt;a href=&quot;#_footnoteref_4&quot;&gt;4&lt;/a&gt;. This one in particular is at full health.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_5&quot;&gt;
&lt;a href=&quot;#_footnoteref_5&quot;&gt;5&lt;/a&gt;. That&amp;#8217;s the weight class marker. The cross-hatched diamond signifies the &quot;assault&quot; class, the heaviest one in MWO.
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Combining Python assignment expressions and destructuring: Having your variable and eating it too</title>
      <link>https://xn--mikoak-6db.net/blog/2023/a-python-assign-destructure-combo.html</link>
      <pubDate>Sun, 6 Aug 2023 00:00:00 +0200</pubDate>
      <guid isPermaLink="false">blog/2023/a-python-assign-destructure-combo.html</guid>
      	<description>
	&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;intro&quot;&gt;Intro&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;One of Python&amp;#8217;s probably most &quot;wow, this is great!&quot; features for newcomers is destructuring in assignments, whether of tuples, lists, or – indeed – any iterables:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;destructuring_intro.py&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;a, b = (&quot;a&quot;, &quot;b&quot;)
[zero, one, two] = range(3)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;For the longest time, this destructuring, and &lt;code&gt;for&lt;/code&gt; comprehensions, were, sadly, all that Python had to offer in terms &quot;structurally-oriented&quot; syntactic sugar. In contrast, Scala&amp;#8217;s &lt;a href=&quot;https://docs.scala-lang.org/tour/pattern-matching.html&quot;&gt;pattern matching&lt;/a&gt;, while arriving a bit later in the game, has always been more feature-packed and functional.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Fortunately for Python, it has been eventually extended with &lt;a href=&quot;https://peps.python.org/pep-0622&quot;&gt;structural pattern matching&lt;/a&gt;, which brings it way closer to the expression power of Scala&amp;#8217;s pattern matching. &quot;Almost&quot; for several reason, the largest being that structural pattern matches are &lt;em&gt;statements&lt;/em&gt;, &lt;strong&gt;not&lt;/strong&gt; expressions. &lt;em&gt;Why&lt;/em&gt; this was done was actually addressed in the PEP &lt;a href=&quot;https://peps.python.org/pep-0622/#make-it-an-expression&quot;&gt;here&lt;/a&gt;; personally, the argument, possibly paraphrased as &quot;it wouldn&amp;#8217;t be Pythonic&quot; is as lost on me as the opportunity that was available here.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In any case&amp;#8230;&amp;#8203; this post is not about structural patching matching, but closing &lt;em&gt;another&lt;/em&gt; type of expression power gap between Python&amp;#8217;s and Scala&amp;#8217;s destructuring semantics, also becoming a reality relatively recently: with Python 3.8.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;admonitionblock note&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td class=&quot;icon&quot;&gt;
&lt;i class=&quot;fa icon-note&quot; title=&quot;Note&quot;&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class=&quot;content&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;By the way: this is definitely a shorter entry, written simply because I stumbled upon the relevant problem (and solution) while writing a &lt;em&gt;considerably&lt;/em&gt; longer series of blogs. Hopefully, they&amp;#8217;ll start appearing this quarter – but until then, let me show you something&amp;#8230;&amp;#8203;&lt;/p&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;schrödinger_s_destructuring&quot;&gt;Schrödinger&amp;#8217;s destructuring&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We&amp;#8217;ll start with an example in Scala&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_1&quot; class=&quot;footnote&quot; href=&quot;#_footnote_1&quot; title=&quot;View footnote.&quot;&gt;1&lt;/a&gt;]&lt;/sup&gt;. Let&amp;#8217;s say we have a three-element list, and we want to assign said elements into separate variables. We end up with something like this:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;pattern_matching_simple.scala&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;val List(one, two, three) = List(1,2,3)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The code above maps easily to Python, with the addition of the &lt;code&gt;val&lt;/code&gt; keyword (for final, i.e. non-reassignable variables) and the syntax of defining a list being the only two differences. Indeed, the corresponding Python code looks like this:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;[one, two, three] = [1, 2, 3]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;OK, let&amp;#8217;s say we know want to store &lt;strong&gt;both&lt;/strong&gt; the individual elements and the entire list. In Scala, this is done easily enough with the addition of an &lt;code&gt;@&lt;/code&gt; assignment:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;pattern_matching_whole.scala&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;val result@List(one, two, three) = List(1,2,3)

// In the console, this prints out:
//
// result: List[Int] = List(1, 2, 3)
// one: Int = 1
// two: Int = 2
// three: Int = 3&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;So what about Python? Until a couple of years ago, all one could do is something like this:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;result = [1, 2, 3]
[one, two, three] = result&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Not exactly the end of the world, but a bit annoying.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Things have changed with the introduction of &lt;a href=&quot;https://peps.python.org/pep-0572&quot;&gt;assignment expressions&lt;/a&gt;, which you may be familiar with, by way of &lt;code&gt;:=&lt;/code&gt;, i.e. the walrus operator&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_2&quot; class=&quot;footnote&quot; href=&quot;#_footnote_2&quot; title=&quot;View footnote.&quot;&gt;2&lt;/a&gt;]&lt;/sup&gt;. As the name suggests, these are simply assignments (&lt;code&gt;=&lt;/code&gt;) that are also &lt;em&gt;expressions&lt;/em&gt; – meaning they evaluate to some value, unlike the standard assignment &lt;em&gt;statement&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Let&amp;#8217;s have a first go at trying to use an assignment expression in our test case:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;result = ([one, two, three] := [1, 2, 3])&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The snippet emulates Scala&amp;#8217;s ordering of assignments, and &lt;em&gt;looks&lt;/em&gt; like it could work, except the interpreter prints out:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  File &quot;&amp;lt;stdin&amp;gt;&quot;, line 1
    result = ([one, two, three] := [1, 2, 3])
              ^^^^^^^^^^^^^^^
SyntaxError: cannot use assignment expressions with list&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The same (or similar) gets printed out for tuples, sets, and so on. Using destructuring in an assignment &lt;strong&gt;expression&lt;/strong&gt; is simply not supported. Not all is lost, however. All we have to do is reverse the sequence of assignments:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;pattern_matching_whole.py&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;[one, two, three] = (result := [1, 2, 3])&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;And this works! Here&amp;#8217;s the corresponding, verifying console output:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; one
1
&amp;gt;&amp;gt;&amp;gt; two
2
&amp;gt;&amp;gt;&amp;gt; three
3
&amp;gt;&amp;gt;&amp;gt; result
[1, 2, 3]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;collapsing_the_wave_function&quot;&gt;Collapsing the wave function&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;To summarize – for a given value, what we wanted is to assign:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;that full value, and&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;a destructured version of the value,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;in one step.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Our solution boils down to using an assignment expression together with good-old destructuring, represented by the general pattern below:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;destructure_assign_pattern.py&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&amp;lt;destructuring_vars&amp;gt; = (&amp;lt;whole_value_vars&amp;gt; := &amp;lt;value_to_destructure&amp;gt;)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Again, this is possible in any version of Python starting from 3.8, inclusive.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;One important remark is that, like in the &quot;old&quot; two-liner version, we actually reference the original value within the assignment expression. Said identity has consequences when the value is mutable, such as the list in the example below:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;destructure_assign_gotcha.py&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;org_value = [1, 2, 3]
[one, two, three] = (result := org_value)
result.append(4)
print(org_value)
# prints:
# [1, 2, 3, 4]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This &lt;em&gt;might&lt;/em&gt; be a disadvantage when rewriting code where, originally, the &quot;whole result&quot; is reconstructed from the destructured, constituent parts, like so:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;destructure_assign_reconstruct.py&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;[one, two, three] = [1, 2, 3]
result = [one, two, three]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;creating a brand-new list. In other words, be wary of the quirk when rewriting code to this pattern.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Now, to get to a final talking point, one that&amp;#8217;s probably on a number of the readers&apos; heads: &lt;strong&gt;should&lt;/strong&gt; this kind of &quot;trick&quot;/pattern be used?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;For starters, I am definitely not a &quot;pure&quot; Python programmer (nor, TBH, do I aim to be one), and so no authority of whether something is &quot;Pythonic&quot; or not.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Otherwise, it can be argued that this pattern sacrifices readability for the sake of terseness – but the same could be said about any use of the assignment expression. The syntax in question is indeed a very sharp tool in the developer&amp;#8217;s drawer, able to muddle up the codebase if abused. However, it still exists for a reason, and that reason is that sometimes these &quot;shortcuts&quot; do &lt;strong&gt;improve&lt;/strong&gt; readability by increasing proximity of the assignment to other, relevant parts of the code.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;For me specifically, the need arose when I was transforming OpenCV&amp;#8217;s &lt;a href=&quot;https://docs.opencv.org/4.7.0/da/d0c/tutorial_bounding_rects_circles.html&quot; target=&quot;_blank&quot;&gt;bounding boxes&lt;/a&gt;. The BBs are encoded as a 4-tuple&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_3&quot; class=&quot;footnote&quot; href=&quot;#_footnote_3&quot; title=&quot;View footnote.&quot;&gt;3&lt;/a&gt;]&lt;/sup&gt;; I had a scenario where I needed to process both meaningfully-named, individual components of the BB, and the entire BB tuple.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The function in question contained less than 10 LoCs, so the call, in my mind, was simple. And this is probably the answer to the question posited above: when following general software engineering practices (including considering your team&amp;#8217;s style and abilities), it&amp;#8217;s OK to use the pattern whenever necessary and practical.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Hope this window into the possibilities offered by Python&amp;#8217;s expanding syntax will prove useful to at least some readers. Happy coding!&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;admonitionblock important&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td class=&quot;icon&quot;&gt;
&lt;i class=&quot;fa icon-important&quot; title=&quot;Important&quot;&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class=&quot;content&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;PS. To be completely clear: I categorically &lt;strong&gt;do not&lt;/strong&gt; claim &quot;discovery&quot; of this pattern, and I frankly strongly doubt I was the first to describe it. I simply haven&amp;#8217;t seen – or don&amp;#8217;t remember seeing – it being discussed anywhere, hence this blog entry :).&lt;/p&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_1&quot;&gt;
&lt;a href=&quot;#_footnoteref_1&quot;&gt;1&lt;/a&gt;. Don&amp;#8217;t worry, the examples in Scala will be simple and completely explained.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_2&quot;&gt;
&lt;a href=&quot;#_footnoteref_2&quot;&gt;2&lt;/a&gt;. To finalize my rant from the intro, note how this PEP – introducing a brand-new way of &quot;converting&quot; what was always a statement in Python to an expression – was defined and implemented much earlier than the structural pattern matching one.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_3&quot;&gt;
&lt;a href=&quot;#_footnoteref_3&quot;&gt;3&lt;/a&gt;. X, Y, width, height
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Blog update</title>
      <link>https://xn--mikoak-6db.net/blog/2023/blog-update.html</link>
      <pubDate>Sun, 6 Aug 2023 00:00:00 +0200</pubDate>
      <guid isPermaLink="false">blog/2023/blog-update.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;OK, it&amp;#8217;s been almost 5 years since something has been published here – it&amp;#8217;s high time for an update.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;First of all, it&amp;#8217;s necessary to clarify that it&amp;#8217;s not like there hasn&amp;#8217;t been any writing in that time. I&amp;#8217;ve exclusively (although unintentionally) been focusing on posting entries on Medium. You can view &lt;a href=&quot;https://medium.com/@mikolaj.koziarkiewicz&quot;&gt;all of those on my Medium profile&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Since I&amp;#8217;m slowly getting back into an increased blog publishing cadence&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_1&quot; class=&quot;footnote&quot; href=&quot;#_footnote_1&quot; title=&quot;View footnote.&quot;&gt;1&lt;/a&gt;]&lt;/sup&gt;, I&amp;#8217;m also ramping up the activity here, mostly with the following rule: this site is going to be the primary publishing target. Medium is going to receive the same blog posts (at least for now), but with a delay.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Speaking of which, a new blog post with some &lt;strong&gt;actual&lt;/strong&gt; content :) will be up later today.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_1&quot;&gt;
&lt;a href=&quot;#_footnoteref_1&quot;&gt;1&lt;/a&gt;. Hey, any positive number is greater than 0!
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Travesty 0.9 now available!</title>
      <link>https://xn--mikoak-6db.net/blog/2018/travesty-release-0.9.html</link>
      <pubDate>Thu, 15 Mar 2018 00:00:00 +0100</pubDate>
      <guid isPermaLink="false">blog/2018/travesty-release-0.9.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;em&gt;(See &lt;a href=&quot;https://mikołak.net/blog/2018/travesty-release-0.7.html&quot;&gt;the original blog entry&lt;/a&gt; to learn about Travesty in general)&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;First of all, supported Akka versions have been extended up to 2.5.11 .&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Most importantly, Travesty can now &lt;strong&gt;generate diagrams from &lt;em&gt;any&lt;/em&gt; graph&lt;/strong&gt;, not only &lt;code&gt;RunnableGraph&lt;/code&gt; / &lt;code&gt;Graph[ClosedShape]&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Here&amp;#8217;s an example of a complex &lt;code&gt;Flow&lt;/code&gt; shape being rendered:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot; style=&quot;text-align: center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//travesty_example_partial_graph.svg&quot; alt=&quot;travesty example partial graph&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As you can see, any open inlets and outlets will be marked automatically - and the relevant labels are configurable.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Hop onto the &lt;a href=&quot;https://github.com/mikolak-net/travesty&quot; target=&quot;_blank&quot;&gt;PROJECT PAGE&lt;/a&gt; for download and usage
instructions!&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Travesty 0.8 now available!</title>
      <link>https://xn--mikoak-6db.net/blog/2018/travesty-release-0.8.html</link>
      <pubDate>Sun, 11 Feb 2018 00:00:00 +0100</pubDate>
      <guid isPermaLink="false">blog/2018/travesty-release-0.8.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;em&gt;(See &lt;a href=&quot;https://mikołak.net/blog/2018/travesty-release-0.7.html&quot;&gt;the previous blog entry&lt;/a&gt; to learn about Travesty in general)&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The probably most important addition is preliminary &lt;strong&gt;typed&lt;/strong&gt; diagram/graph support.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;You need to add a bit of boilerplate to make it possible, but that just boils down to importing
&lt;code&gt;net.mikolak.travesty.registry._&lt;/code&gt; and using either the &lt;code&gt;.register&lt;/code&gt; or &lt;code&gt;.↓&lt;/code&gt; extension methods on your stages.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;For example, this:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;import net.mikolak.travesty
import net.mikolak.travesty.OutputFormat
import registry._ //adds special .↓ and .register methods to stages

val graph = Source.single(&quot;1&quot;).↓.via(Flow[String].map(_.toInt).↓).to(Sink.seq)

//render as image to file, PNG is supported as well
travesty.toFile(graph, OutputFormat.SVG)(&quot;/tmp/stream.svg&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Generates a diagram with appropriate element types:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot; style=&quot;text-align: center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//travesty_example_typed_graph.svg&quot; alt=&quot;travesty example typed graph&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Another major change is the ability to select which Graphviz engine is used.
Specifically, if you have Graphviz installed on the target machine, you can add an &lt;code&gt;application.conf&lt;/code&gt;
with:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;application.conf&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-hocon&quot; data-lang=&quot;hocon&quot;&gt;travesty {
    engines = [CommandLine]
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;for a &lt;strong&gt;10x+&lt;/strong&gt; speedup!&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;0.8 also features a fix for the &lt;a href=&quot;https://github.com/mikolak-net/travesty/issues/4&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;Transform&lt;/code&gt; bug&lt;/a&gt;,
and support for Akka 2.5.8, as well as 2.5.9 .&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Hop onto the &lt;a href=&quot;https://github.com/mikolak-net/travesty&quot; target=&quot;_blank&quot;&gt;PROJECT PAGE&lt;/a&gt; for download and usage
instructions!&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Happy diagramming!&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Travesty released!</title>
      <link>https://xn--mikoak-6db.net/blog/2018/travesty-release-0.7.html</link>
      <pubDate>Fri, 12 Jan 2018 00:00:00 +0100</pubDate>
      <guid isPermaLink="false">blog/2018/travesty-release-0.7.html</guid>
      	<description>
	&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;what&quot;&gt;What?&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Travesty is a Scala library for doing two things with Akka Streams:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;generating (untyped) structural diagrams of your Akka Streams, both as graphics and text (the latter
useful for logging).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;generating &lt;a href=&quot;https://tinkerpop.apache.org/&quot; target=&quot;_blank&quot;&gt;Tinkerpop 3 / Gremlin&lt;/a&gt; graph data structures - usable for e.g. writing tests that check stage sequencing in dynamically constructed Streams.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As you can imagine, I&amp;#8217;ve originally developed this library with the growing
stream structure of the &lt;a href=&quot;https://mikołak.net/blog/2016/akka-streams-libgx-1.html&quot;&gt;&quot;Akka Streams meets LibGDX&quot;&lt;/a&gt; blogpost series. Here&amp;#8217;s how the diagram for the current structure looks like:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot; style=&quot;text-align: center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//travesty_example_graph.svg&quot; alt=&quot;travesty example graph&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;where_to_get_it&quot;&gt;Where to get it?&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Head over to the &lt;a href=&quot;https://github.com/mikolak-net/travesty&quot; target=&quot;_blank&quot;&gt;GitHub repo&lt;/a&gt;, where you&amp;#8217;ll find installation and usage instructions.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Reactive Streams in Scala meet a Game Engine - Part 7</title>
      <link>https://xn--mikoak-6db.net/blog/2017/akka-streams-libgdx-7.html</link>
      <pubDate>Fri, 3 Nov 2017 00:00:00 +0100</pubDate>
      <guid isPermaLink="false">blog/2017/akka-streams-libgdx-7.html</guid>
      	<description>
	&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;series_table_of_contents&quot;&gt;Series&apos; Table of Contents&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2016/akka-streams-libgdx-1.html&quot;&gt;Part 1 - Introduction&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2016/akka-streams-libgdx-2.html&quot;&gt;Part 2 - Defining Requirements&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2017/akka-streams-libgdx-3.html&quot;&gt;Part 3 - Setting Up the Project&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2017/akka-streams-libgdx-4.html&quot;&gt;Part 4 - Coding the Tick Source&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2017/akka-streams-libgdx-5.html&quot;&gt;Part 5 - Making the Game Live&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2017/akka-streams-libgdx-6.html&quot;&gt;Part 6 - (De)Composition is key&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2017/akka-streams-libgdx-7.html&quot;&gt;Part 7 - Finally, interactivity!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;in_this_episode&quot;&gt;In this episode&amp;#8230;&amp;#8203;&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We&amp;#8217;re going to add input and interactivity to our game.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;admonitionblock important&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td class=&quot;icon&quot;&gt;
&lt;i class=&quot;fa icon-important&quot; title=&quot;Important&quot;&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class=&quot;content&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As a reminder&amp;#8230;&amp;#8203;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We would like to have a game that:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;requires relatively quick player reaction (so e.g. no turn-based games),&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;has a lot of events happening at any given moment,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;and employs a large number of entities to do so.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Recall also one of our initial assumptions: mistakes will be made prominent (and prominently made), including potentially avoidable ones, in order to showcase them and their solutions.&lt;/p&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;getting_input_in&quot;&gt;Getting input&amp;#8230;&amp;#8203; in&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;LibGDX has two input modes - a &lt;a href=&quot;https://github.com/libgdx/libgdx/wiki/Polling&quot; target=&quot;_blank&quot;&gt;polling one&lt;/a&gt; and
an &lt;a href=&quot;https://github.com/libgdx/libgdx/wiki/Event-handling&quot; target=&quot;_blank&quot;&gt;event-based one&lt;/a&gt;. The polling one can not only introduce inefficiencies, but also has the problem that:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Caution: If you rely on polling, you might miss events, e.g. a fast paced key down/key up.
If you need to make sure a specific sequence of input action was completed, use event handling instead.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;So let&amp;#8217;s go with event handling.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In terms of supporting it, all we have to do is to create and register a listener. That&amp;#8217;s simple enough, but there&amp;#8217;s one
more question - how do we transfer the events to the stream?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Well, we already have a solution - use a &lt;code&gt;Source.actorRef[KeyboardInput]&lt;/code&gt;, where &lt;code&gt;KeyboardInput&lt;/code&gt; is our message type.
So, all we need to do is to make a listener that &quot;forwards&quot; input events to a provided actor&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_1&quot; class=&quot;footnote&quot; href=&quot;#_footnote_1&quot; title=&quot;View footnote.&quot;&gt;1&lt;/a&gt;]&lt;/sup&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Here&amp;#8217;s the relevant code.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;KeyboardProxy.scala&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;sealed trait KeyboardInput &lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
case class KeyUp(keycode: Int) extends KeyboardInput
case class KeyDown(keycode: Int) extends KeyboardInput
case class KeyTyped(character: Char) extends KeyboardInput

import akka.actor.ActorRef
import com.badlogic.gdx.InputAdapter

class KeyboardProxy(outActor: ActorRef) extends InputAdapter { &lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
  override def keyUp(keycode: Int) = {
    outActor ! KeyUp(keycode) &lt;i class=&quot;conum&quot; data-value=&quot;3&quot;&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
    true
  }

  override def keyTyped(character: Char) = {
    outActor ! KeyTyped(character) &lt;i class=&quot;conum&quot; data-value=&quot;3&quot;&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
    true
  }

  override def keyDown(keycode: Int) = {
    outActor ! KeyUp(keycode) &lt;i class=&quot;conum&quot; data-value=&quot;3&quot;&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
    true
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;colist arabic&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Since LibGDX has no &quot;input event data&quot; types, we&amp;#8217;ll make our own.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Taking in the &lt;code&gt;ActorRef&lt;/code&gt; to forward to.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;3&quot;&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Forwarding key events to the actor.&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Let&amp;#8217;s go back to &lt;code&gt;MainScreen&lt;/code&gt; and add a new &lt;code&gt;Source&lt;/code&gt; for our events:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;BulletHell.scala&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;case class GameState(delta: TickDelta, bodies: List[Body], events: List[KeyboardInput] = List.empty) &lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;colist arabic&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GameState&lt;/code&gt; now supports listing current input events.&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;BulletHell.scala&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;class MainScreen extends ScreenAdapter {
....
  val inputSource = Source.actorRef[KeyboardInput](bufferSize = 0, OverflowStrategy.dropTail) &lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;colist arabic&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dropTail&lt;/code&gt; since we want to react to latest input events in case of lag.&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Let&amp;#8217;s now modify our graph to integrate the source. Unfortunately there&amp;#8217;s quite a few moving parts that can&amp;#8217;t really
be separated into shorter snippets - note that comments are out of sequence with the code, but instead roughly
follow the design flow.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;BulletHell.scala&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;    val inputLogger = (g: GameState) =&amp;gt; { () =&amp;gt; &lt;i class=&quot;conum&quot; data-value=&quot;7&quot;&gt;&lt;/i&gt;&lt;b&gt;(7)&lt;/b&gt;
      {
        if (g.events.nonEmpty) {
          println(g.events)
        }
      }
    }

    val bufferSize = 100

    val graph =
      tickSource
        .zipWithMat(
          inputSource
            .batch(bufferSize, List(_))(_ :+ _) &lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
            .prepend(Source.single(List.empty[KeyboardInput]))  &lt;i class=&quot;conum&quot; data-value=&quot;3&quot;&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
            .expand(elem =&amp;gt;
              Iterator.single(elem) ++ Iterator.continually(List.empty[KeyboardInput])) &lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
        )((gs, es) =&amp;gt; gs.copy(events = es))(Keep.both) &lt;i class=&quot;conum&quot; data-value=&quot;4&quot;&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
        .via(setUpLogic(List(generator, mover, worldUpdater, tickIncrementer, inputLogger)))
        .toMat(Sink.queue())(Keep.both)

    val ((sourceActor, inputActor), sinkQueue) = graph.run() &lt;i class=&quot;conum&quot; data-value=&quot;5&quot;&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;

    tickActor = Some(sourceActor)
    actionQueue = Some(sinkQueue)
    Gdx.input.setInputProcessor(new KeyboardProxy(inputActor)) &lt;i class=&quot;conum&quot; data-value=&quot;6&quot;&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;colist arabic&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://doc.akka.io/api/akka/2.5.6/akka/stream/scaladsl/FlowOps.html#batch[S](max:Long,seed:Out=%3ES)(aggregate:(S,Out)=%3ES):FlowOps.this.Repr[S]&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;batch&lt;/code&gt;&lt;/a&gt;
 does just that, it batches up elements when the downstream backpressures. In this case we&amp;#8217;re simply batching
by creating a &lt;code&gt;List&lt;/code&gt;. Note that you will &lt;strong&gt;always&lt;/strong&gt; get a &lt;code&gt;List[KeyboardInput]&lt;/code&gt; down the line - so when there&amp;#8217;s no backpressure,
a list with a single element will be sent.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://doc.akka.io/api/akka/2.5.6/akka/stream/scaladsl/FlowOps.html#expand[U](extrapolate:Out=%3EIterator[U]):FlowOps.this.Repr[U]&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;expand&lt;/code&gt;&lt;/a&gt;
is there to do the opposite of &lt;code&gt;batch&lt;/code&gt; - if downstream signals demand, &lt;code&gt;expand&lt;/code&gt; will produce additional &quot;synthetic&quot;
elements from the generated iterator. Its syntax is a bit weird: if you want to preserve the original element, it has to be
produced as the first element of the iterator.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;3&quot;&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;We need this because, currently, there&amp;#8217;s no way of providing &lt;code&gt;expand&lt;/code&gt; with a &quot;seed&quot; element in case when downstream signals
demand at the very start&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_2&quot; class=&quot;footnote&quot; href=&quot;#_footnote_2&quot; title=&quot;View footnote.&quot;&gt;2&lt;/a&gt;]&lt;/sup&gt;. If we want the very first tick to go through,
we need to use something like &lt;a href=&quot;https://doc.akka.io/api/akka/2.5.6/akka/stream/scaladsl/FlowOps.html#prepend[U%3E:Out,Mat2](that:akka.stream.Graph[akka.stream.SourceShape[U],Mat2]):FlowOps.this.Repr[U]&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;prepend&lt;/code&gt;&lt;/a&gt;.
Note that this means the first tick will &lt;strong&gt;always&lt;/strong&gt; be paired with an empty input list - but this is fine for our needs.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;4&quot;&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Here the argument part of the &lt;code&gt;zipMat&lt;/code&gt; ends - we&amp;#8217;re joining up the two sources by adding the list of events to the
given &lt;code&gt;GameState&lt;/code&gt;, and asking the materialization logic to keep both &lt;code&gt;ActorRefs&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;5&quot;&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Why a nested tuple and not a triple? Because that&amp;#8217;s how we ordered our materialization process - we could convert it into a triple,
but there&amp;#8217;s no point - since Scala&amp;#8217;s pattern matching is smart enough to help us deconstruct the value anyway.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;6&quot;&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Here we register our &lt;code&gt;KeyboardProxy&lt;/code&gt; listener as an LibGDX input handler. Note that it&amp;#8217;s here that we use the &lt;code&gt;inputActor&lt;/code&gt;
that&amp;#8217;s generated by the &lt;code&gt;Source[KeyboardInput]&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;7&quot;&gt;&lt;/i&gt;&lt;b&gt;7&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;A debug log for the events.&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;interlude_a_failure_to_communicate&quot;&gt;Interlude: A Failure to Communicate&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;the_problem&quot;&gt;The Problem&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Now, you&amp;#8217;ll occasionally notice the game will blackscreen and fail with the following message (or similar) :&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;[game-akka.actor.default-dispatcher-2]
 [akka://game/user/StreamSupervisor-0/flow-0-2-actorRefSource]
 Dropping element because there is no downstream demand: [GameState(2.14698E-4,List(),List())]&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Since it&amp;#8217;s from an &lt;code&gt;actorRefSource&lt;/code&gt; and the type is &lt;code&gt;GameState&lt;/code&gt;, we know this is from our &lt;code&gt;tickSource&lt;/code&gt;. Let&amp;#8217;s see where
the message originates from ultimately. Here&amp;#8217;s the offending code in Akka&amp;#8217;s source:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;akka/ActorRefSourceActor.scala&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;      if (totalDemand &amp;gt; 0L)
        onNext(elem)
      else if (bufferSize == 0)
        log.debug(&quot;Dropping element because there is no downstream demand: [{}]&quot;, elem)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;as you can see, for this to trigger, the demand &lt;strong&gt;and&lt;/strong&gt; buffer must be &lt;code&gt;0&lt;/code&gt;. Regarding the demand, going back to our &lt;code&gt;render&lt;/code&gt; method:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;BulletHell.scala&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;    tickActor.foreach { actor =&amp;gt;
      val bodyArray = ArrayGdx.of(classOf[Body])
      world.getBodies(bodyArray)
      actor ! GameState(delta, bodyArray.asScala.toList) &lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
    }
&lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
    import scala.concurrent.Await.result

    for {
      q &amp;lt;- actionQueue
      actions &amp;lt;- result(q.pull(), Duration.Inf) &lt;i class=&quot;conum&quot; data-value=&quot;3&quot;&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
      a &amp;lt;- actions
    } {
      a()
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;colist arabic&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;We&amp;#8217;re sending our message here.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;It arrives somewhere here. No demand was signalled to the stage at this time and the &lt;code&gt;TickDelta&lt;/code&gt; element is dropped, and not passed to the subsequent stages.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;3&quot;&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;We&amp;#8217;re waiting infinitely here.&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;finding_the_solution_source_analysis&quot;&gt;Finding the solution - source analysis&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;What signals demand? Multiple stages can do that - &lt;code&gt;actionQueue.pull()&lt;/code&gt; is an obvious candidate, but if you muck around with the source code, you&amp;#8217;ll find that &lt;code&gt;ZipWith&lt;/code&gt; does so as well:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;akka/ZipWith2&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;    override def preStart(): Unit = {
      pull(in0)
      pull(in1)
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In fact, if we create a simple example with a pulled-from &lt;code&gt;Sink.queue()&lt;/code&gt;, or a &lt;code&gt;zipWith&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;object StreamPlay extends App {

  implicit val actorSystem = ActorSystem()
  implicit val mat = ActorMaterializer()

  val (tickSource, actionQueue) = Source.actorRef[String](0, OverflowStrategy.dropNew)
    //.zipWith(Source.single(&quot;BLAH&quot;))((_,_)) &lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
    .toMat(Sink.queue())(Keep.both).run()

  //actionQueue.pull() &lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
  Thread.sleep(100)
  tickSource ! &quot;BLAH&quot;

}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;colist arabic&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Uncommenting this&amp;#8230;&amp;#8203;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&amp;#8230;&amp;#8203;or this will cause the message to not be dropped (at least on a reasonably fast machine).&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;and a sufficient timeout will make the stream work.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;That answers the &lt;code&gt;demand == 0&lt;/code&gt; origin question. Of course, the &lt;code&gt;bufferSize == 0&lt;/code&gt; problem is our own damn fault:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;BulletHell.scala&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;  val tickSource = Source.actorRef[GameState](bufferSize = 0, OverflowStrategy.dropNew)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Switching to &lt;code&gt;bufferSize = 1&lt;/code&gt; will make the problem go away. The relevant commit, with the fix and antecedent changes
&lt;a href=&quot;https://github.com/mikolak-net/scala-stream-bullet-hell/tree/cc1eea689dcf2e9f8675f40473158187e640439b&quot; target=&quot;_blank&quot;&gt;is here&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;finding_the_solution_debugging&quot;&gt;Finding the solution - debugging&lt;/h3&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;the_buffet&quot;&gt;The buffet&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Of course, taking a deductive stroll through the source code is refreshing every now and again, but quickly becomes tedious
in day-to-day work. It would be good, then, to have a tool that helps us with that.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Unfortunately, at least as open source tools go, there&amp;#8217;s not much to use. Sure, there &lt;a href=&quot;https://github.com/timcharper/reactive-viz&quot; target=&quot;_blank&quot;&gt;are&lt;/a&gt; &lt;a href=&quot;https://github.com/13h3r2/akka-stream-xray&quot; target=&quot;_blank&quot;&gt;a few&lt;/a&gt;, but they&amp;#8217;re hopelessly outdated.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Now, falling back to plain Akka, we have options such as &lt;a href=&quot;http://kamon.io/documentation/kamon-akka/0.6.6/overview/&quot; target=&quot;_blank&quot;&gt;Kamon&lt;/a&gt; and &lt;a href=&quot;https://github.com/ouven/akka-visualmailbox&quot; target=&quot;_blank&quot;&gt;akka-visualmailbox&lt;/a&gt;. Due to relative ease of setup, we&amp;#8217;ll go with the latter.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;So, we only need to add:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;application.conf&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;akka.actor.default-mailbox.mailbox-type = &quot;de.aktey.akka.visualmailbox.VisualMailboxType&quot;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;run the visualization container image:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;console&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;docker run -ti --rm -p 8080:8080 -p 60009:60009/udp ouven/akka-visual-mailbox-visualization&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;and open the browser on &lt;code&gt;&lt;a href=&quot;http://localhost:8080&quot; class=&quot;bare&quot;&gt;http://localhost:8080&lt;/a&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;However, once we run the (non-buggy iteration of) program, we get something like:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot; style=&quot;text-align: center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//stream-game//akka_visualmaiilbox_autofusing.png&quot; alt=&quot;akka visualmaiilbox autofusing&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Where are the other stages?&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;getting_to_the_point&quot;&gt;Getting to the point&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Currently, Akka Streams employs &quot;auto-fusing&quot; per default. In simple terms, these means that
stages that would be executed sequentially are &quot;joined&quot; (or, indeed, &quot;fused&quot;) into a single actor
that does the same work. More details can be found &lt;a href=&quot;https://doc.akka.io/docs/akka/2.5/scala/stream/stream-flows-and-basics.html#operator-fusion&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The simple workaround is to insert &lt;code&gt;async&lt;/code&gt; boundaries into our stream, &quot;forcing&quot; the materializer to
instantiate separate actors. And this is how it looks like:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;BulletHell.scala&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;      tickSource.async &lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
        .zipWithMat(
          inputSource.async &lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
            .batch(bufferSize, List(_))(_ :+ _)
            .prepend(Source.single(List.empty[KeyboardInput]))
            .expand(elem =&amp;gt;
              Iterator.single(elem) ++ Iterator.continually(List.empty[KeyboardInput]))
        )((gs, es) =&amp;gt; gs.copy(events = es))(Keep.both)
          .async &lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
        .via(setUpLogic(List(generator, mover, worldUpdater, tickIncrementer, inputLogger)))
          .async.toMat(Sink.queue())(Keep.both) &lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;colist arabic&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Manually inserted boundaries, stopping the materializer from fusing the stages.&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;And now we finally can see the &quot;normal&quot; state:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot; style=&quot;text-align: center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//stream-game//akka_visualmaiilbox_nonfusing_normal.png&quot; alt=&quot;akka visualmaiilbox nonfusing normal&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;as well as the failure condition:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot; style=&quot;text-align: center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//stream-game//akka_visualmaiilbox_nonfusing_buggy.png&quot; alt=&quot;akka visualmaiilbox nonfusing buggy&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;alternatives&quot;&gt;Alternatives?&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The only viable alternative is Kamon.
My colleague &lt;a href=&quot;https://softwaremill.com/akka-monitoring-with-kamon-part-1&quot; target=&quot;_blank&quot;&gt;has provided an overview of that
lib already&lt;/a&gt;, so - given that Kamon also only provides a &quot;basic-Akka-level&quot; view of things - I&amp;#8217;ll
defer those interested to that.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;ok_what_the_hell&quot;&gt;OK - what the hell?&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;It seems like we have two more problems now:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;The support for sensible (and open-source) Akka Streams debugging is poor.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We got hit with an unexpected problem completely out of the blue, with seemingly no warning from the library.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The 1st issue unfortunately remains a fact of life for the time being. One probable reason is the
internal complexity of Akka Streams - due to the materialization mechanism there are additional layers
of abstraction to consider and interpret.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As already said, the blame for the 2nd problem lies squarely on us - not only the error condition is dully
logged by Akka, we should have seen it coming when we used an &lt;code&gt;OverflowStrategy&lt;/code&gt; with &lt;code&gt;drop&lt;/code&gt; in the name.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;admonitionblock note&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td class=&quot;icon&quot;&gt;
&lt;i class=&quot;fa icon-note&quot; title=&quot;Note&quot;&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class=&quot;content&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The takeway here is:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;use &lt;code&gt;DEBUG&lt;/code&gt; mode for local development (applies for most dev work really, regardless of particular libraries),&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;avoid defining empty buffers, especially for &quot;externally communicating&quot; stages (such as &lt;code&gt;Source.actorRef&lt;/code&gt;), unless you know what you&amp;#8217;re doing.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;making_the_input_count&quot;&gt;Making the input count&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Let&amp;#8217;s add some actual interactivity into the mix. Keeping it simple, we&amp;#8217;ll just add an additional, differently-sized sphere that is controllable via key presses.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The expedient solution is to modify our &lt;code&gt;createSphere&lt;/code&gt; method, adding two arguments:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;one that determines the size of the sphere,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;another that makes the &quot;player&quot; and &quot;enemy&quot; objects distinct somehow.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The second one is a bit non trivial. We could pop yet another piece of data into our &lt;code&gt;GameState&lt;/code&gt;, but there&amp;#8217;s
a quick alternative - each &lt;code&gt;Body&lt;/code&gt; contains a &lt;code&gt;userData&lt;/code&gt; attribute that is specifically intended to attach custom information to the entities, so let&amp;#8217;s use that.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;First, we need a coproduct/enum for our representation:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;BodyType.scala&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;sealed trait BodyType
case object Player extends BodyType
case object Enemy extends BodyType&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Then, we modify our &lt;code&gt;createSphere&lt;/code&gt; accordingly:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;BulletHell.scala&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;  private def createSphere(center: Vector2, bodyType: BodyType, radius: Float = 1) = { &lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
    val bodyDef = new BodyDef
    bodyDef.`type` = BodyType.DynamicBody
    bodyDef.position.set(center)

    val circle = new CircleShape()
    circle.setRadius(radius) &lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;

    val fixtureDef = new FixtureDef()
    fixtureDef.shape = circle
    fixtureDef.density = 1f

    val body = world.createBody(bodyDef)
    body.createFixture(fixtureDef)
    fixtureDef.shape.dispose()
    body.setUserData(bodyType) &lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
  }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;colist arabic&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Modified lines.&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Next, we augment our generator function:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;    val generator = (g: GameState) =&amp;gt; { () =&amp;gt;
      {
        if (g.bodies.isEmpty) {
          for (_ &amp;lt;- 1 to config.world.gen.NumCircles) {
            val randomLocation = new Vector2(Random.nextInt(config.world.Width.size),
                                             Random.nextInt(config.world.Height.size))
            createSphere(randomLocation, Enemy) &lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
          }

          //player body
          createSphere(new Vector2(config.world.Width.size, config.world.Height.size).scl(0.5f),
                       Player,
                       radius = 2f) &lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
        }
      }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;colist arabic&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Adding the marker type here&amp;#8230;&amp;#8203;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&amp;#8230;&amp;#8203;and the player-controlled object here, twice as large and placed in the middle of the play area initially.&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As well as the mover function:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;...
          for (body &amp;lt;- g.bodies if body.getUserData == Enemy) { &lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;colist arabic&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;We now only want the non-player entities to be self-propelled, of course.&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Finally, we add a handler function for the input:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;    val inputHandler = (g: GameState) =&amp;gt; { () =&amp;gt;
      {
        for {
          playerBody &amp;lt;- g.bodies.find(_.getUserData == Player)
          e &amp;lt;- g.events
        } {
          val inputMults = e match {
            case KeyUp(keycode) =&amp;gt; &lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
              keycode match {
                case Keys.LEFT  =&amp;gt; (-1f, 0f) &lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
                case Keys.RIGHT =&amp;gt; (1f, 0f)
                case Keys.UP    =&amp;gt; (0f, 1f)
                case Keys.DOWN  =&amp;gt; (0f, -1f)
                case _          =&amp;gt; (0f, 0f)
              }
            case _ =&amp;gt; (0f, 0f)
          }

          val v = (new Vector2(_: Float, _: Float)).tupled(inputMults).scl(1000f) &lt;i class=&quot;conum&quot; data-value=&quot;3&quot;&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
          playerBody.applyForceToCenter(v, true) &lt;i class=&quot;conum&quot; data-value=&quot;4&quot;&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
        }
      }
    }

...
        .via(setUpLogic(List(generator, mover, worldUpdater, tickIncrementer, inputHandler))) &lt;i class=&quot;conum&quot; data-value=&quot;5&quot;&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;colist arabic&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;We&amp;#8217;re interested only in &lt;code&gt;KeyUp&lt;/code&gt; events (arbitrary, it could have also been &lt;code&gt;KeyDown&lt;/code&gt;, as long as we pick one).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The events are encoded into a tuple that represents the (X,Y) directional displacement of our sphere.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;3&quot;&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The tuples are converted into a sufficiently large force vector&amp;#8230;&amp;#8203;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;4&quot;&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&amp;#8230;&amp;#8203;which is applied to the controllable sphere.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;5&quot;&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Let&amp;#8217;s not forget to add this function to our logic stream (we also &lt;em&gt;removed&lt;/em&gt; the logger).&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As you can see, this works without any problems:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;videoblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;video src=&quot;../../illustrations//stream-game//source-input.mp4&quot; width=&quot;852&quot; autoplay controls loop&gt;
Your browser does not support the video tag.
&lt;/video&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/mikolak-net/scala-stream-bullet-hell/tree/01d10b051e634cb985152287f30dd2c1026e540c&quot; target=&quot;_blank&quot;&gt;Here&amp;#8217;s the relevant commit.&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;discussion&quot;&gt;Discussion&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;At this time it might be interesting to see how other &quot;reactive&quot; approaches handle time-sensitive input.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;During &lt;a href=&quot;https://youtu.be/B72a1D2xvFs?t=14m12s&quot; target=&quot;_blank&quot;&gt;the relevant talk Aleksandar Prokopec&lt;/a&gt; describes the handling of input via a combinations of:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Reactive&lt;/code&gt;s - congruous to Rx&amp;#8217;s &lt;code&gt;Observable&lt;/code&gt;s and Stream&amp;#8217;s stages, and&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Signal&lt;/code&gt;s - which seem to correspond to Rx&amp;#8217;s &lt;a href=&quot;http://reactivex.io/documentation/subject.html&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;BehaviorSubject&lt;/code&gt;&lt;/a&gt; or Stream&amp;#8217;s &lt;code&gt;expand&lt;/code&gt; &lt;code&gt;Flow&lt;/code&gt; stage &lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_3&quot; class=&quot;footnote&quot; href=&quot;#_footnote_3&quot; title=&quot;View footnote.&quot;&gt;3&lt;/a&gt;]&lt;/sup&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/jmhofer/rxjava-libgdx&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;rxjava-libgdx&lt;/code&gt;&lt;/a&gt; just uses a system of &lt;code&gt;Observable&lt;/code&gt;s which are
based on the &quot;reification-of-input&quot; approach that we also used here (i.e. converting listener method calls into discrete data objects).&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;what_s_next&quot;&gt;What&amp;#8217;s next?&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As a reminder, we&amp;#8217;ve mentioned the following problems in the preceding part:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;the &lt;code&gt;worldUpdater&lt;/code&gt; relies on closing over the &lt;code&gt;World&lt;/code&gt; instead of being supplied with it;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;similarly, the &lt;code&gt;tickIncrementer&lt;/code&gt; not only reads but also &lt;strong&gt;changes&lt;/strong&gt; an &quot;external&quot; value;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the rendering logic is outside the system, because it needs to be invoked in a specific
sequence in relation to the other operations, and we have no control over that sequence currently;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;related to all of the above, we have no way of signalling the stream setup logic what information
we have in our executor functions;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;due to the lack of a definable ordering, we also run the risk of race conditions, &lt;strong&gt;despite&lt;/strong&gt; everything
being &quot;executed&quot; in the same thread - e.g. if we remove a body in one place, it can still be
referenced in another, resulting in a crash of the app;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;we&amp;#8217;re starting to have a god object, it would be good to remove the entire &quot;stream engine&quot;
portion out of &lt;code&gt;MainScreen&lt;/code&gt; soon.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;If, right now, you&amp;#8217;re staring dumbfounded at this list, wondering how we managed to solve any of these problems - then you&amp;#8217;re on the right track!&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The truth is, keyboard input integration has taken too much effort to tackle the pertinent problems. However, in the immortal words of &lt;a href=&quot;https://en.wikiquote.org/wiki/Bob_Ross&quot; target=&quot;_blank&quot;&gt;Bob Ross&lt;/a&gt;, this is a &quot;happy accident&quot;. Why?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Well, let&amp;#8217;s see:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;we now see that issue 4. will come to bite us in the hindquarters if we don&amp;#8217;t do anything about it - already we needed to smuggle metadata in LibGDX&amp;#8217;s objects, and this was only a trivial marker - what about potentially needed stuff such as health points, movement energy, etc. etc.?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the same applies to point 5.: we now have a specific, &lt;em&gt;immediate&lt;/em&gt; problem to tackle - what will happen once we add collision detection and health point depletion? In our current state - chaos!&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Furthermore (and related to point 5.), if you compare our current solution to other &quot;reactive&quot; implementation, you&amp;#8217;ll find that it&amp;#8217;s quite inflexible - in particular, it doesn&amp;#8217;t support any sort of combinators (&lt;code&gt;.map&lt;/code&gt;, &lt;code&gt;.flatMap&lt;/code&gt;, &lt;code&gt;.filter&lt;/code&gt;, etc. etc.). Taking together that conundrum with the previously described issue, the following are all examples of
&quot;component&quot; types we want to support:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;(List[Body]) =&amp;gt; () =&amp;gt; Unit &lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
(List[Body]) =&amp;gt; SomeData &lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
(SomeData) =&amp;gt; () =&amp;gt; Unit &lt;i class=&quot;conum&quot; data-value=&quot;3&quot;&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
Flow[(SomeData, KeybardInput), () =&amp;gt; Unit, _] &lt;i class=&quot;conum&quot; data-value=&quot;4&quot;&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
Flow[(List[Body], TickDelta), SomeData, _] &lt;i class=&quot;conum&quot; data-value=&quot;4&quot;&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;colist arabic&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Sometimes we wont need the tick info&amp;#8230;&amp;#8203;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&amp;#8230;&amp;#8203;sometimes we&amp;#8217;d want to create custom data&amp;#8230;&amp;#8203;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;3&quot;&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&amp;#8230;&amp;#8203;sometimes all we need is that custom data&amp;#8230;&amp;#8203;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;4&quot;&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&amp;#8230;&amp;#8203;and sometimes we&amp;#8217;d wish to use the power of stage combinators.&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;At the end of the day, we have a more comprehensive view of what we need to clean up, and more importantly, how. And that&amp;#8217;s what the next part will (now, definitely, I promise!) cover.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;That&amp;#8217;s not to say we haven&amp;#8217;t accomplished anything - we now enjoy at least a minimal semblance of interactivity, and, so far, the code runs relatively smoothly. With that in mind, see you again soon!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_1&quot;&gt;
&lt;a href=&quot;#_footnoteref_1&quot;&gt;1&lt;/a&gt;. A fancy name for this kind of logic is &quot;reification&quot;, i.e. &quot;making a &lt;em&gt;real thing&lt;/em&gt; out of something&quot; - in this case, making discrete objects out of method calls.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_2&quot;&gt;
&lt;a href=&quot;#_footnoteref_2&quot;&gt;2&lt;/a&gt;. although a PR for that is in the works.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_3&quot;&gt;
&lt;a href=&quot;#_footnoteref_3&quot;&gt;3&lt;/a&gt;. It&amp;#8217;s difficult to say where the analogies lie in the actually-released library, i.e. &lt;a href=&quot;http://reactors.io/tutorialdocs//reactors/reactors/index.html&quot; target=&quot;_blank&quot;&gt;Reactors&lt;/a&gt;, as the terminology is different and apparently non-isomorphic to the one in the talk
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Reactive Streams in Scala meet a Game Engine - Part 6</title>
      <link>https://xn--mikoak-6db.net/blog/2017/akka-streams-libgdx-6.html</link>
      <pubDate>Sun, 3 Sep 2017 00:00:00 +0200</pubDate>
      <guid isPermaLink="false">blog/2017/akka-streams-libgdx-6.html</guid>
      	<description>
	&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;series_table_of_contents&quot;&gt;Series&apos; Table of Contents&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2016/akka-streams-libgdx-1.html&quot;&gt;Part 1 - Introduction&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2016/akka-streams-libgdx-2.html&quot;&gt;Part 2 - Defining Requirements&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2017/akka-streams-libgdx-3.html&quot;&gt;Part 3 - Setting Up the Project&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2017/akka-streams-libgdx-4.html&quot;&gt;Part 4 - Coding the Tick Source&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2017/akka-streams-libgdx-5.html&quot;&gt;Part 5 - Making the Game Live&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2017/akka-streams-libgdx-6.html&quot;&gt;Part 6 - (De)Composition is key&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2017/akka-streams-libgdx-7.html&quot;&gt;Part 7 - Finally, interactivity!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;in_this_episode&quot;&gt;In this episode&amp;#8230;&amp;#8203;&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We&amp;#8217;re going to make the first step in reorganizing our stream to be more modular and thread-safe.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;admonitionblock important&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td class=&quot;icon&quot;&gt;
&lt;i class=&quot;fa icon-important&quot; title=&quot;Important&quot;&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class=&quot;content&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As a reminder&amp;#8230;&amp;#8203;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We would like to have a game that:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;requires relatively quick player reaction (so e.g. no turn-based games),&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;has a lot of events happening at any given moment,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;and employs a large number of entities to do so.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Recall also one of our initial assumptions: mistakes will be made prominent (and prominently made), including potentially avoidable ones, in order to showcase them and their solutions.&lt;/p&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;modularizing_the_game_s_logic&quot;&gt;Modularizing the game&amp;#8217;s logic&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Currently the vast majority of everything that &quot;happens&quot; in our game is located in one giant
block of code. This is unsustainable, and not what we want in the long run.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;So, let&amp;#8217;s split up the singular flow stage. Ideally, it&amp;#8217;s broadly two-phased:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;One where everything except thread-sensitive stuff happens;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the other where all actions bound to the UI thread happen.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The simplest way to implement the structure defined above is to generate functions in the first phase, and then invoke them in the second.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot; style=&quot;text-align: center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//stream-game//first_modularization_sketch.svg&quot; alt=&quot;first modularization sketch&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 1. Sketch of our planned approach.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Code-wise, we should start with defining an alias, for convenience&amp;#8217;s sake :&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;BulletHell.scala&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;class MainScreen extends ScreenAdapter {
   type Action = () =&amp;gt; Unit
...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Now, let&amp;#8217;s code in the &quot;engine&quot; of the first phase:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;BulletHell.scala&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;class MainScreen extends ScreenAdapter {
...
  private def setUpLogic(
      elements: List[(GameState) =&amp;gt; Action]): &lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
        Flow[GameState, Seq[Action], NotUsed] =
    Flow.fromGraph(GraphDSL.create() { implicit b =&amp;gt;
      import GraphDSL.Implicits._ &lt;i class=&quot;conum&quot; data-value=&quot;3&quot;&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
      val scatter = b.add(Broadcast[GameState](elements.size)) &lt;i class=&quot;conum&quot; data-value=&quot;4&quot;&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
      val gather = b.add(ZipN[Action](elements.size)) &lt;i class=&quot;conum&quot; data-value=&quot;5&quot;&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;

      for (e &amp;lt;- elements) {
        scatter ~&amp;gt; b.add(Flow.fromFunction(e)) ~&amp;gt; gather &lt;i class=&quot;conum&quot; data-value=&quot;6&quot;&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
      }

      FlowShape(scatter.in, gather.out) &lt;i class=&quot;conum&quot; data-value=&quot;7&quot;&gt;&lt;/i&gt;&lt;b&gt;(7)&lt;/b&gt;
    })
...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;colist arabic&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;We&amp;#8217;re accepting a list of functions that create actions based on our current &lt;code&gt;GameState&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&amp;#8230;&amp;#8203;and returning a &lt;code&gt;Flow&lt;/code&gt; that aggregates the resulting actions into one sequence per step.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;3&quot;&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Akka Streams&apos; Graph DSL boilerplate.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;4&quot;&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;Broadcast&lt;/code&gt; stage simply provides &lt;code&gt;n&lt;/code&gt; outlets with every input element.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;5&quot;&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;ZipN&lt;/code&gt; stage waits for every one of its &lt;code&gt;n&lt;/code&gt; inlets to produce an element, and aggregates
them into a sequence.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;6&quot;&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Wiring everything together&amp;#8230;&amp;#8203;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;7&quot;&gt;&lt;/i&gt;&lt;b&gt;7&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&amp;#8230;&amp;#8203;and producing a &quot;black box&quot; blueprint with all the logic hidden behind the &lt;code&gt;FlowShape&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Now let&amp;#8217;s rewrite our logic into a series of modules and replace our original &lt;code&gt;tickFlow&lt;/code&gt; with
the new logic flow:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;BulletHell.scala&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;  override def show() = {
  ...
    val generator = (g: GameState) =&amp;gt; { () =&amp;gt; &lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
      {
        if (g.bodies.isEmpty) {
          for (_ &amp;lt;- 1 to config.world.gen.NumCircles) {
            val randomLocation = new Vector2(Random.nextInt(config.world.Width.size),
                                             Random.nextInt(config.world.Height.size))
            createSphere(randomLocation)
          }
        }
      }
    }

    val mover = (g: GameState) =&amp;gt; {
      import config.world.gen
      def randomForceComponent = Random.nextInt(2 * gen.MaxForce) - gen.MaxForce

      () =&amp;gt;
        {
          for (body &amp;lt;- g.bodies) {
            if (tick % gen.ForceApplyTickInterval == 0) {
              body.applyForceToCenter(randomForceComponent, randomForceComponent, true)
            }
          }
        }
    }

    val tickIncrementer = (_: GameState) =&amp;gt; { () =&amp;gt;
      {
        tick += 1
      }
    }

    val worldUpdater = (g: GameState) =&amp;gt; { () =&amp;gt;
      {
        world.step(g.delta, 6, 2)
      }
    }

    val graph = tickSource
      .via(setUpLogic(List(generator, mover, worldUpdater, tickIncrementer)))
      .to(Sink.ignore)
  ...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;colist arabic&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Every component now returns a function that can be called later.&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;There&amp;#8217;s still one loose end, namely actually &lt;strong&gt;invoking&lt;/strong&gt; the generated actions.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;introducing_more_thread_safety&quot;&gt;Introducing more thread safety&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;There are two ways to do that.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;using_materialization&quot;&gt;Using materialization&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We&amp;#8217;ve already taken into advantage of materialization to obtain an input to our stream,
by way of a materialized &lt;code&gt;ActorRef&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;However, &lt;a href=&quot;http://doc.akka.io/docs/akka/current/scala/stream/stream-composition.html#materialized-values&quot; target=&quot;_blank&quot;&gt;materialization can
provide an arbitrary number of values&lt;/a&gt;, as illustrated by this diagram from the docs&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_1&quot; class=&quot;footnote&quot; href=&quot;#_footnote_1&quot; title=&quot;View footnote.&quot;&gt;1&lt;/a&gt;]&lt;/sup&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot; style=&quot;text-align: center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//stream-game//akka_streams_compose_mat.png&quot; alt=&quot;akka streams compose mat&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 2. How materialized value composition works in Akka Streams &lt;em&gt;(Copyright 2017 Lightbend, licensed under Apache 2&lt;/em&gt;).&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We turn to &lt;a href=&quot;http://doc.akka.io/docs/akka/current/scala/stream/stages-overview.html&quot;&gt;the nice list
of available stages&lt;/a&gt;, again, from the docs&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_2&quot; class=&quot;footnote&quot; href=&quot;#_footnote_2&quot; title=&quot;View footnote.&quot;&gt;2&lt;/a&gt;]&lt;/sup&gt;.
Happily, we find there&amp;#8217;s a couple of sink stages that produce &quot;dynamic&quot; output, e.g. &lt;code&gt;actorRef&lt;/code&gt; which is a
dual to &lt;code&gt;Source.actorRef&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;However, the most convenient method for us here is &lt;a href=&quot;http://doc.akka.io/docs/akka/current/scala/stream/stages-overview.html#queue&quot;&gt;&lt;code&gt;Sink.queue&lt;/code&gt;&lt;/a&gt;.
Materializing it generates a &lt;code&gt;SinkQueue&lt;/code&gt; that can be polled for every separate element that arrives at the sink.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Let&amp;#8217;s add a storage &lt;code&gt;var&lt;/code&gt; for this queue to our &lt;code&gt;MainScreen&lt;/code&gt;, as well as a type alias for abstracting away implementation details:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;BulletHell.scala&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;class MainScreen extends ScreenAdapter {

  ...
  type ActionQueue = SinkQueueWithCancel[Seq[Action]]

  ...
  var actionQueue: Option[ActionQueue] = None&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Now all that&amp;#8217;s left is to replace our &lt;code&gt;Sink.ignore&lt;/code&gt; with the queue&amp;#8230;&amp;#8203;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;BulletHell.scala&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;  override def show() = {
  ...

    val graph = tickSource
      .via(setUpLogic(List(generator, mover, worldUpdater, tickIncrementer)))
      .toMat(Sink.queue())(Keep.both) &lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;

    val (sourceActor, sinkQueue) = graph.run()

    tickActor = Some(sourceActor)
    actionQueue = Some(sinkQueue)
  ...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;colist arabic&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;toMat&lt;/code&gt; hints that the materialized value from the sink should be preserved. &lt;code&gt;Keep.both&lt;/code&gt; is a second
argument of &lt;code&gt;toMat&lt;/code&gt;, instructing that the &quot;original&quot;/&quot;left&quot; value should be combined with the &quot;other&quot;/&quot;right&quot;
value to a tuple.&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&amp;#8230;&amp;#8203;and invoke the resultant actions:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;BulletHell.scala&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;  override def render(delta: TickDelta) = {
    ...

    import scala.concurrent.Await.result

    for {
      q &amp;lt;- actionQueue &lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
      actions &amp;lt;- result(q.pull(), Duration.Inf) &lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
      a &amp;lt;- actions
    } {
      a() &lt;i class=&quot;conum&quot; data-value=&quot;3&quot;&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
    }
  ...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;colist arabic&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Since &lt;code&gt;actionQueue&lt;/code&gt; is an &lt;code&gt;Option&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;pull()&lt;/code&gt; method returns a value of &lt;code&gt;Future[Option[Seq[Action]]]&lt;/code&gt;, so we need to
wait for the result &lt;strong&gt;and&lt;/strong&gt; deconstruct the &lt;code&gt;Option&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;3&quot;&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Each action gets invoked here.&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The code is available under &lt;a href=&quot;https://github.com/mikolak-net/scala-stream-bullet-hell/tree/830a9407b3013bf6fad6ec51d16df73732169718&quot; target=&quot;_blank&quot;&gt;this commit&lt;/a&gt;. If you check out and execute this ref, you&amp;#8217;ll find our game
runs exactly the same, meaning we accomplished our goal.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;assigning_a_custom_dispatcher&quot;&gt;Assigning a custom dispatcher&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;em&gt;(This part is optional to the understanding of the text as a whole.)&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;On first glance, it seems it would be possible to instead define a &lt;code&gt;Sink.foreach&lt;/code&gt; stage with a
dispatcher that runs on the game&amp;#8217;s UI thread.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Furthermore, it sounds like the Testkits&amp;#8217;s &lt;a href=&quot;http://doc.akka.io/docs/akka/current/scala/testing.html#callingthreaddispatcher&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;CallingThreadDispatcher&lt;/code&gt;&lt;/a&gt;
 would be the way to go. Let&amp;#8217;s try that out&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;BulletHell.scala&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;    val graph = tickSource
      .via(setUpLogic(List(generator, mover, worldUpdater, tickIncrementer))) &lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
      .to(Sink.foreach[Seq[Action]](actions =&amp;gt; {
        actions.foreach(_()) &lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
        println(Thread.currentThread().getName) &lt;i class=&quot;conum&quot; data-value=&quot;3&quot;&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
      }).withAttributes(Attributes(Dispatcher(CallingThreadDispatcher.Id)))) &lt;i class=&quot;conum&quot; data-value=&quot;4&quot;&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;


    tickActor = Some(graph.run()) &lt;i class=&quot;conum&quot; data-value=&quot;5&quot;&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;colist arabic&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Same code as in the &lt;code&gt;SinkQueue&lt;/code&gt; variant.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Iterating and calling all actions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;3&quot;&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Debug statement to see whether we&amp;#8217;re on the current thread.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;4&quot;&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Dispatcher&amp;#8217;s in Akka are created dynamically from the configuration and referenced by IDs.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;5&quot;&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Going back to the same code as in the previous part of this blog.&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;However, when we run it, we would see e.g.:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;game-akka.actor.default-dispatcher-4
game-akka.actor.default-dispatcher-3
game-akka.actor.default-dispatcher-4
game-akka.actor.default-dispatcher-3
game-akka.actor.default-dispatcher-4&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;whereas the UI thread is named &lt;code&gt;LWJGL Application&lt;/code&gt;. If we think for a minute, that makes sense - the &lt;strong&gt;calling&lt;/strong&gt;
thread here is a thread from the pool used by the &lt;strong&gt;previous&lt;/strong&gt; stages (the ones generating the actions,
or rather the &lt;code&gt;ZipN&lt;/code&gt; substage).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;So, what we would need to do is to setup a custom dispatcher that would enqueue &lt;code&gt;Runnable&lt;/code&gt; s &lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_3&quot; class=&quot;footnote&quot; href=&quot;#_footnote_3&quot; title=&quot;View footnote.&quot;&gt;3&lt;/a&gt;]&lt;/sup&gt;, and expose
a method named e.g. &lt;code&gt;popAndRun()&lt;/code&gt; that would take the first &lt;code&gt;Runnable&lt;/code&gt; out of the queue and execute it in the current
thread. This &lt;code&gt;popAndRun()&lt;/code&gt; would then be invoked during &lt;code&gt;MainScreen#render()&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;However, since dispatchers &lt;a href=&quot;https://stackoverflow.com/questions/38336012/create-akka-dispatcher-programmatically&quot; target=&quot;_blank&quot;&gt;cannot be created
programmatically&lt;/a&gt;, we would need to employ some trickery in order to invoke such a method. I think it
would work, specifically through the &lt;a href=&quot;http://doc.akka.io/docs/akka/current/scala/dispatchers.html#looking-up-a-dispatcher&quot;&gt;&lt;code&gt;Dispatcher&lt;/code&gt;-lookup system&lt;/a&gt;
and several casts, but would be a hacky way to implement what we &lt;strong&gt;already&lt;/strong&gt; have achieved anyway.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The only advantage of that would be that we stay within the Akka Streams API directly (as opposed to
the queue materialization approach). However, I don&amp;#8217;t think this alone merits further investigation into
a kludgy alternative implementation.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;loose_ends&quot;&gt;Loose ends&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Whatever the approach, we can see that the current architecture isn&amp;#8217;t without problems:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;the &lt;code&gt;worldUpdater&lt;/code&gt; relies on closing over the &lt;code&gt;World&lt;/code&gt; instead of being supplied with it;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;similarly, the &lt;code&gt;tickIncrementer&lt;/code&gt; not only reads but also &lt;strong&gt;changes&lt;/strong&gt; an &quot;external&quot; value;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the rendering logic is outside the system, because it needs to be invoked in a specific
sequence in relation to the other operations, and we have no control over that sequence currently;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;related to all of the above, we have no way of signalling the stream setup logic what information
we have in our executor functions;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;due to the lack of a definable ordering, we also run the risk of race conditions, &lt;strong&gt;despite&lt;/strong&gt; everything
being &quot;executed&quot; in the same thread - e.g. if we remove a body in one place, it can still be
referenced in another, resulting in a crash of the app;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;we&amp;#8217;re starting to have a god object, it would be good to remove the entire &quot;stream engine&quot;
portion out of &lt;code&gt;MainScreen&lt;/code&gt; soon.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In the next part, we will be solving these problems, and also adding some input to the game,
finally making interactive. To that end, we&amp;#8217;ll be using architecture templates that app developers find
familiar (as an exercise, you may try to figure out what they will be given the current state of our codebase and the
enumerated problems).&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_1&quot;&gt;
&lt;a href=&quot;#_footnoteref_1&quot;&gt;1&lt;/a&gt;. On the other hand, if you find yourself using materialized values for anything other than inputs and outputs, consider that a code smell.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_2&quot;&gt;
&lt;a href=&quot;#_footnoteref_2&quot;&gt;2&lt;/a&gt;. Although it&amp;#8217;s sadly missing links to the actual implementations.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_3&quot;&gt;
&lt;a href=&quot;#_footnoteref_3&quot;&gt;3&lt;/a&gt;. To be specific, the &lt;code&gt;ExecutorServiceFactory&lt;/code&gt; generated by its &lt;code&gt;ExecutorServiceFactoryProvider&lt;/code&gt; would do that.
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Reactive Streams in Scala meet a Game Engine - Part 5</title>
      <link>https://xn--mikoak-6db.net/blog/2017/akka-streams-libgdx-5.html</link>
      <pubDate>Sun, 20 Aug 2017 00:00:00 +0200</pubDate>
      <guid isPermaLink="false">blog/2017/akka-streams-libgdx-5.html</guid>
      	<description>
	&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;series_table_of_contents&quot;&gt;Series&apos; Table of Contents&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2016/akka-streams-libgdx-1.html&quot;&gt;Part 1 - Introduction&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2016/akka-streams-libgdx-2.html&quot;&gt;Part 2 - Defining Requirements&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2017/akka-streams-libgdx-3.html&quot;&gt;Part 3 - Setting Up the Project&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2017/akka-streams-libgdx-4.html&quot;&gt;Part 4 - Coding the Tick Source&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2017/akka-streams-libgdx-5.html&quot;&gt;Part 5 - Making the Game Live&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2017/akka-streams-libgdx-6.html&quot;&gt;Part 6 - (De)Composition is key&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2017/akka-streams-libgdx-7.html&quot;&gt;Part 7 - Finally, interactivity!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;in_this_episode&quot;&gt;In this episode&amp;#8230;&amp;#8203;&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We&amp;#8217;re finally making our game &lt;strong&gt;do something&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;admonitionblock important&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td class=&quot;icon&quot;&gt;
&lt;i class=&quot;fa icon-important&quot; title=&quot;Important&quot;&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class=&quot;content&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As a reminder&amp;#8230;&amp;#8203;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We would like to have a game that:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;requires relatively quick player reaction (so e.g. no turn-based games),&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;has a lot of events happening at any given moment,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;and employs a large number of entities to do so.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Recall also one of our initial assumptions: mistakes will be made prominent (and prominently made), including potentially avoidable ones, in order to showcase them and their solutions.&lt;/p&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;a_bit_of_refactoring&quot;&gt;A bit of refactoring&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;magicks&quot;&gt;Magicks!&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We&amp;#8217;re now well into our development of our little game, which merits a critical
look back into what we&amp;#8217;ve coded in so far.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Principally, we seem to have a bit of magical numerology in our dimensional definition, specifically
in both our code and the desktop version:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;core/BullletHell.scala&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;camera.setToOrtho(false, 800, 480)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;desktop/Main.scala&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;cfg.height = 480
cfg.width = 800&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;They&amp;#8217;re both in sore need to be moved to a globally settable constant value.
However, they both seem to be aligned in form and function, so let&amp;#8217;s create a
value class that can hold them:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;case class Dim(d: Float) extends AnyVal { &lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
  def size = d.toInt
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;colist arabic&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Defining a &lt;a href=&quot;http://docs.scala-lang.org/overviews/core/value-classes.html&quot; target=&quot;_blank&quot;&gt;value class&lt;/a&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;I confess here on cheating a little bit - I know this the, erm, value of this value
class is going to shine through in a moment. However, I&amp;#8217;d like to point out that
nevertheless value classes are cheap not only in terms of &lt;strong&gt;writing&lt;/strong&gt; them,
but also in terms of executing the relevant code. Because all this nets
executable code within static invocations, as long as user-defined value classes
can be used, they remain useful entities to base our domain model upon on.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Come worst case, time spent writing them will be undone by (sometimes literally) a couple of
editor actions.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;reflowing&quot;&gt;Reflowing!&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;That still leaves us the problem of centralizing our generalized code. We could make a proper config,
but a quick compromise trick, useful for sketching out configs, is to store everything in a nested
&lt;code&gt;object&lt;/code&gt;. In our case, this means:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;object config {
  object world {
    val Width = Dim(160)
    val Height = Dim(100)
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The pattern has several advantages:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;requires no additional dependencies,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;it provides practically all of the benefits of a &quot;proper&quot; config during the stage of development where
code modifications are abundant,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;since config keys are actually Scala stable identifiers, code assist/renaming etc. works out of the box,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;once the point of diminishing returns vs a &quot;proper&quot; config is reached, a swap to it can be effected via
a relatively simple regex.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;So, our calling code is now:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;core/BullletHell.scala&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;camera.setToOrtho(false, config.world.width.size, config.world.height.size)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We&amp;#8217;re also going to decouple the size of the desktop window and increase it a bit:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;desktop/Main.scala&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;cfg.height = 640
cfg.width = 1024&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Note that &lt;code&gt;1024/640 = 160/100 = 1.6&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;OK, but why 160 x 100?&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;adding_box2d_basics&quot;&gt;Adding Box2D basics&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Well, our next change will be introducing physical objects into our game, and, for that,
we&amp;#8217;re be going with a physics module that integrates Box2D with LibGDX.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://box2d.org/about/&quot; target=&quot;_blank&quot;&gt;Box2D&lt;/a&gt; is a widely-used C++ framework for simulating physics in 2 dimensions (as the
name suggests). It is both simple and fairly powerful. However, as the &lt;a href=&quot;https://github.com/erincatto/Box2D/wiki/FAQ&quot; target=&quot;_blank&quot;&gt;FAQ outlines&lt;/a&gt;,
non-stationary objects should be no larger than around 10 units/meters&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_1&quot; class=&quot;footnote&quot; href=&quot;#_footnote_1&quot; title=&quot;View footnote.&quot;&gt;1&lt;/a&gt;]&lt;/sup&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;So, the size of 160 x 100 is sufficient to have interactable, visible objects in the sweet spot of Box2D
preferred sizes, and conforms to the aspect ration of our viewable window.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Having said that, let&amp;#8217;s now add the following:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;core/BullletHell.scala&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;val world = new World(new Vector2(0, 0), false) &lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;

val debugRenderer = new Box2DDebugRenderer() &lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;colist arabic&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;This is the core Box2D management object. The first argument describes the gravity vector (we want to
have a weightless environment, so it&amp;#8217;s &lt;code&gt;(0,0)&lt;/code&gt;), the second is a flag to enable an optimization technique.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;A very useful class when prototyping, it draws wireframes of all physics objects for you.&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Before we modify our Flow, we need to include enhance the tick element to account for our extended game state.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Specifically, we need to include at minimum all Box2D &lt;em&gt;bodies&lt;/em&gt; currently present. Bodies represent physical
objects in Box2D.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;So, instead of a simple &lt;code&gt;TickDelta,&lt;/code&gt; we&amp;#8217;ll now be producing this:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;case class GameState(delta: TickDelta, bodies: List[Body])&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Let&amp;#8217;s change the &lt;code&gt;render&lt;/code&gt; function to account for that:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;core/BullletHell.scala&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;override def render(delta: TickDelta) = {

    tickActor.foreach { actor =&amp;gt;
      val bodyArray = ArrayGdx.of(classOf[Body])
      world.getBodies(bodyArray) &lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
      actor ! GameState(delta, bodyArray.asScala.toList)
    }

    ...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;colist arabic&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Song-and-dance necessary to get all current objects - this pass-by-reference approach is both due to Box2D C++
roots and for efficiency&amp;#8217;s sake.&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Now we modify our flow. For starters, let&amp;#8217;s create a couple of circular bodies in random places. Here&amp;#8217;s
how a utility method for creating a new circle body looks like:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;core/BullletHell.scala&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;  private def createSphere(center: Vector2) = {
    val bodyDef = new BodyDef &lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
    bodyDef.`type` = BodyType.DynamicBody &lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt; &lt;i class=&quot;conum&quot; data-value=&quot;3&quot;&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
    bodyDef.position.set(center) &lt;i class=&quot;conum&quot; data-value=&quot;4&quot;&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;

    val circle = new CircleShape()
    circle.setRadius(1)

    val fixtureDef = new FixtureDef() &lt;i class=&quot;conum&quot; data-value=&quot;5&quot;&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
    fixtureDef.shape = circle
    fixtureDef.density = 1f

    val body = world.createBody(bodyDef)
    body.createFixture(fixtureDef)
    fixtureDef.shape.dispose() &lt;i class=&quot;conum&quot; data-value=&quot;6&quot;&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
  }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;colist arabic&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;A body definition is simply an abstract blueprint for a body.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Since &lt;code&gt;type&lt;/code&gt; is a reserved word in Scala, we have to use the backtick notation here.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;3&quot;&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Dynamic bodies are ones that behave exactly how you would expect a physical body to - they are subject
to gravity, collisions with other objects, user-defined forces, etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;4&quot;&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;Vector2&lt;/code&gt; argument is used as the &lt;code&gt;(x,y)&lt;/code&gt; coordinates of the center of our circle.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;5&quot;&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Bodies are composed of one or more &lt;em&gt;fixtures&lt;/em&gt;. In our case we only have one, with a circular shape and a density
of 1 kg/m³ (so e.g. like mainstream American beer).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;6&quot;&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;More game-engine-specific song-and-dance. If we&amp;#8217;d generate a considerable number of these shapes, we&amp;#8217;d cache this
object, but here it&amp;#8217;s unnecessary.&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Let&amp;#8217;s add a couple of new config values:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;object config {
  object world {
    object gen {
      val NumCircles = 3
      val MaxForce = 300
      val ForceApplyTickInterval = 100
    }
    ...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Now we&amp;#8217;re ready to modify our flow. We need to match the new game state object, add some circles in random
positions, and make them move, all in our &lt;code&gt;show&lt;/code&gt; method:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;core/BullletHell.scala&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;val tickSettingFlow = {

  Flow[GameState].map { case gs@GameState(delta, bodies) =&amp;gt; &lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
    if(bodies.isEmpty) { &lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
      for(_ &amp;lt;- 1 to config.world.gen.NumCircles) {
        val randomLocation = new Vector2(
                                Random.nextInt(config.world.Width.size),
                                Random.nextInt(config.world.Height.size)) &lt;i class=&quot;conum&quot; data-value=&quot;3&quot;&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
        createSphere(randomLocation)
      }
    } else { &lt;i class=&quot;conum&quot; data-value=&quot;4&quot;&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
      import config.world.gen &lt;i class=&quot;conum&quot; data-value=&quot;5&quot;&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
      def randomForceComponent = Random.nextInt(2*gen.MaxForce)-gen.MaxForce

      for(body &amp;lt;- bodies) {
        if(tick % gen.ForceApplyTickInterval == 0) { &lt;i class=&quot;conum&quot; data-value=&quot;6&quot;&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
          body.applyForceToCenter(randomForceComponent, randomForceComponent, true)
        }
      }
    }

    world.step(delta, 6, 2) &lt;i class=&quot;conum&quot; data-value=&quot;7&quot;&gt;&lt;/i&gt;&lt;b&gt;(7)&lt;/b&gt;
    tick += 1
    gs
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;colist arabic&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Matching to the new game state.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Starting situation, lets create our bodies.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;3&quot;&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Completely random location within our world.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;4&quot;&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Our game logic &quot;proper&quot;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;5&quot;&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;As mentioned before, the &quot;quick config&quot; scheme gives us the advantage of value keys being stable
identifiers, this is an example of what this approach provides.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;6&quot;&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;We randomly apply a force every X ticks.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;7&quot;&gt;&lt;/i&gt;&lt;b&gt;7&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;World&lt;/code&gt; needs to be manually &quot;stepped over&quot;. The second and third parameters are the recommended
values for the engine&amp;#8217;s constraint solver.&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We also have to add this:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;debugRenderer.render(world, camera.combined)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;to the end of our &lt;code&gt;show&lt;/code&gt; method to fire up the aforementioned renderer.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;And this is basically it! Here&amp;#8217;s how a possible run looks like:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;videoblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;video src=&quot;../../illustrations//stream-game//box2d-start.mp4&quot; width=&quot;852&quot; controls loop&gt;
Your browser does not support the video tag.
&lt;/video&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;exception_reporting&quot;&gt;Exception reporting&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As a final touch for this episode, let&amp;#8217;s add a decider for our stream. We need it, because Akka Streams
has one notorious design quirk - exceptions are silently swallowed by default &lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_2&quot; class=&quot;footnote&quot; href=&quot;#_footnote_2&quot; title=&quot;View footnote.&quot;&gt;2&lt;/a&gt;]&lt;/sup&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The normal way to go around that is outlined in &lt;a href=&quot;https://stackoverflow.com/questions/35631754/why-is-akka-streams-swallowing-my-exceptions&quot; target=&quot;_blank&quot;&gt;this SO thread&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In our case this would mean:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;core/BullletHell.scala&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;val loggingDecider: Supervision.Decider = { e =&amp;gt;
    println(s&quot;Exception when processing game loop: $e&quot;)
    Supervision.Stop
}
implicit val actorSystem = ActorSystem(&quot;game&quot;)
implicit val materializer = ActorMaterializer(ActorMaterializerSettings(actorSystem)
    .withSupervisionStrategy(loggingDecider))&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Now we&amp;#8217;re sure that if an exception occurs in our flow, we&amp;#8217;ll now about it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The code for the current part is available &lt;a href=&quot;https://github.com/mikolak-net/scala-stream-bullet-hell/commit/a0382273f75c44f6c5562cb54d02cb2eedb7444d&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;next_up&quot;&gt;Next up&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Our flow works, but is still not thread-safe, and essentially one big logic blob. We&amp;#8217;re going to improve that.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_1&quot;&gt;
&lt;a href=&quot;#_footnoteref_1&quot;&gt;1&lt;/a&gt;. Box2D uses meters as size units.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_2&quot;&gt;
&lt;a href=&quot;#_footnoteref_2&quot;&gt;2&lt;/a&gt;. That&amp;#8217;s not strictly true - there are incidental logs that show up on the &lt;code&gt;DEBUG&lt;/code&gt; level, indicating that something happened, but that still does not list the exact cause of why the stream failed.
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>New Layout</title>
      <link>https://xn--mikoak-6db.net/blog/2017/new-layout.html</link>
      <pubDate>Tue, 1 Aug 2017 00:00:00 +0200</pubDate>
      <guid isPermaLink="false">blog/2017/new-layout.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As you may have noticed there has been a layout refresh. The old look was never meant
to be permanent (and, regrettably, last as long as it did), so what you&amp;#8217;re seeing now
is somewhat closer to what I intended to be hear.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Anyway, Part 5 of the Akka Streams/LibGDX series is coming soon. In the meantime, enjoy
the existing content!&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Reactive Streams in Scala meet a Game Engine - Part 4</title>
      <link>https://xn--mikoak-6db.net/blog/2017/akka-streams-libgdx-4.html</link>
      <pubDate>Fri, 7 Jul 2017 00:00:00 +0200</pubDate>
      <guid isPermaLink="false">blog/2017/akka-streams-libgdx-4.html</guid>
      	<description>
	&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;series_table_of_contents&quot;&gt;Series&apos; Table of Contents&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2016/akka-streams-libgdx-1.html&quot;&gt;Part 1 - Introduction&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2016/akka-streams-libgdx-2.html&quot;&gt;Part 2 - Defining Requirements&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2017/akka-streams-libgdx-3.html&quot;&gt;Part 3 - Setting Up the Project&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2017/akka-streams-libgdx-4.html&quot;&gt;Part 4 - Coding the Tick Source&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2017/akka-streams-libgdx-5.html&quot;&gt;Part 5 - Making the Game Live&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2017/akka-streams-libgdx-6.html&quot;&gt;Part 6 - (De)Composition is key&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2017/akka-streams-libgdx-7.html&quot;&gt;Part 7 - Finally, interactivity!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;in_this_episode&quot;&gt;In this episode&amp;#8230;&amp;#8203;&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&amp;#8230;&amp;#8203;we&amp;#8217;re finally ready to add Akka Streams code to our setup, by implementing the heart of our game logic stream - the
tick &lt;code&gt;Source&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;admonitionblock important&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td class=&quot;icon&quot;&gt;
&lt;i class=&quot;fa icon-important&quot; title=&quot;Important&quot;&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class=&quot;content&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As a reminder&amp;#8230;&amp;#8203;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We would like to have a game that:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;requires relatively quick player reaction (so e.g. no turn-based games),&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;has a lot of events happening at any given moment,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;and employs a large number of entities to do so.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Recall also one of our initial assumptions: mistakes will be made prominent (and prominently made), including potentially avoidable ones, in order to showcase them and their solutions.&lt;/p&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;scouting_out_the_solution_space&quot;&gt;Scouting out the solution space&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Let&amp;#8217;s define our core problem first - we need to somehow transfer the information
about each incoming tick into our stream. The first order of business is to find a way
to inject that information into the stream.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;It&amp;#8217;s probably tempting to create a custom publisher-listener solution for that, but
we would wind up reinventing the wheel this way. Instead, let&amp;#8217;s look whether we can find out
how the Akka Streams&apos; API deals with the problem.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As a matter of fact, there is a
&lt;a href=&quot;http://doc.akka.io/api/akka/current/akka/stream/scaladsl/Source$.html#tick[T](initialDelay:scala.concurrent.duration.FiniteDuration,interval:scala.concurrent.duration.FiniteDuration,tick:T):akka.stream.scaladsl.Source[T,akka.actor.Cancellable]^&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;Source.tick&lt;/code&gt;&lt;/a&gt; factory method
that handles a similar scenario. However, the ticks here are generated internally, so it&amp;#8217;s of no use for us.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;However, we see that two other useful factory methods exist:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://doc.akka.io/api/akka/current/akka/stream/scaladsl/Source$.html#actorPublisher[T](props:akka.actor.Props):akka.stream.scaladsl.Source[T,akka.actor.ActorRef]&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;Source.actorPublisher&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://doc.akka.io/api/akka/current/akka/stream/scaladsl/Source$.html#actorRef[T](bufferSize:Int,overflowStrategy:akka.stream.OverflowStrategy):akka.stream.scaladsl.Source[T,akka.actor.ActorRef]&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;Source.actorRef&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Both provide us with an &lt;code&gt;ActorRef&lt;/code&gt; that we can use to pump ticks into our stream.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;admonitionblock note&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td class=&quot;icon&quot;&gt;
&lt;i class=&quot;fa icon-note&quot; title=&quot;Note&quot;&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class=&quot;content&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Recall: in Akka Streams, running
a graph can return a non-unit value - this is called the &lt;em&gt;materialized value&lt;/em&gt; of a graph &lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_1&quot; class=&quot;footnote&quot; href=&quot;#_footnote_1&quot; title=&quot;View footnote.&quot;&gt;1&lt;/a&gt;]&lt;/sup&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Usually, the materialized value is used to get some &quot;result&quot; out from running the graph. However, what&amp;#8217;s important to remember is that
&lt;strong&gt;any&lt;/strong&gt; graph stage can generate a materialized value, not only a &lt;code&gt;Sink&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In fact, it is almost equally frequent to use a materialized value not only to get some output out of the graph, but
also provide input &lt;em&gt;into&lt;/em&gt; it &lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_2&quot; class=&quot;footnote&quot; href=&quot;#_footnote_2&quot; title=&quot;View footnote.&quot;&gt;2&lt;/a&gt;]&lt;/sup&gt;.
This is what we are doing here - once the graph is ran, the &lt;code&gt;ActorRef&lt;/code&gt; we get as the return result serves as our &quot;messenger&quot; between our libgdx code and the
Akka Streams graph.&lt;/p&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We&amp;#8217;re going to investigate both to see whether they help us solve our problem, but first, let&amp;#8217;s&amp;#8230;&amp;#8203;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;setup&quot;&gt;Setup&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;First of all, that &lt;code&gt;Float&lt;/code&gt; for the tick delta time looks ungainly, so we&amp;#8217;ll introduce a package object to deal with that:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;stream_bullethell/package.scala&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;package net.mikolak

package object stream_bullethell {
  type TickDelta = Float
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Next, let&amp;#8217;s create a framework for our stream - we&amp;#8217;re going to be printing ticks from our stream now:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;class MainScreen extends ScreenAdapter {

  lazy val camera = new OrthographicCamera()
  val batch: SpriteBatch = new SpriteBatch()

  var tick = 1L

  implicit val actorSystem = ActorSystem(&quot;game&quot;)
  implicit val materializer = ActorMaterializer() &lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;

  val tickSource: Source[Nothing, ActorRef] = ??? &lt;i class=&quot;conum&quot; data-value=&quot;3&quot;&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
  var tickActor: Option[ActorRef] = None &lt;i class=&quot;conum&quot; data-value=&quot;4&quot;&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;

  lazy val font = {
    val f = new BitmapFont()
    f.getData.setScale(2f)
    f
  }

  override def show() = {
    camera.setToOrtho(false, 800, 480)

    val tickSettingFlow = Flow[TickDelta].map { td =&amp;gt;
      tick += 1
      td
    } &lt;i class=&quot;conum&quot; data-value=&quot;5&quot;&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
    val graph = tickSource.via(tickSettingFlow).to(Sink.ignore) &lt;i class=&quot;conum&quot; data-value=&quot;6&quot;&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;

    tickActor = Some(graph.run()) &lt;i class=&quot;conum&quot; data-value=&quot;7&quot;&gt;&lt;/i&gt;&lt;b&gt;(7)&lt;/b&gt;
  }

  override def render(delta: TickDelta) = {
    tickActor.foreach(_ ! delta) &lt;i class=&quot;conum&quot; data-value=&quot;8&quot;&gt;&lt;/i&gt;&lt;b&gt;(8)&lt;/b&gt;

    //print tick
    Gdx.gl.glClearColor(0, 0, 0.5f, 1)
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
    camera.update()
    batch.setProjectionMatrix(camera.combined)
    batch.begin()
    font.draw(batch, s&quot;Tick: $tick&quot;, 0, font.getCapHeight)
    batch.end()
  }

  override def dispose(): Unit = {
    actorSystem.terminate() &lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;colist arabic&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Standard initialization song-and-dance.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Deinit here &lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_3&quot; class=&quot;footnote&quot; href=&quot;#_footnote_3&quot; title=&quot;View footnote.&quot;&gt;3&lt;/a&gt;]&lt;/sup&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;3&quot;&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Placeholder for our &lt;code&gt;Source&lt;/code&gt;, note the materialized value type (&lt;code&gt;ActorRef&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;4&quot;&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Placeholder for the materialized value.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;5&quot;&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Defining a simple &lt;code&gt;Flow&lt;/code&gt; that increments the &lt;code&gt;tick&lt;/code&gt; value from the Stream level, and passing the &lt;code&gt;TimeDelta&lt;/code&gt; value through.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;6&quot;&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Connecting all parts&amp;#8230;&amp;#8203;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;7&quot;&gt;&lt;/i&gt;&lt;b&gt;7&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&amp;#8230;&amp;#8203;and materializing the graph with &lt;code&gt;run&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;8&quot;&gt;&lt;/i&gt;&lt;b&gt;8&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Here&amp;#8217;s where we communicate the occurrence of a tick with the stream.&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The concept is straightforward: we&amp;#8217;re using a &lt;a href=&quot;http://doc.akka.io/docs/akka/current/scala/stream/stream-composition.html#materialized-values&quot; target=&quot;_blank&quot;&gt;materialized value&lt;/a&gt; (an &lt;code&gt;ActorRef&lt;/code&gt;) to ping the source
with a tick delta time every time a tick occurs.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;You can play around with the source code for this stage &lt;a href=&quot;https://github.com/mikolak-net/scala-stream-bullet-hell/tree/8a386d516535df51be8ff3920f09c743d334df21&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;code_source_actorpublisher_code&quot;&gt;&lt;code&gt;Source.actorPublisher&lt;/code&gt;&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;To use this method, we need to implement an actor with an &lt;code&gt;ActorPublisher&lt;/code&gt; trait.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Basically, we need to create a simple queue implementation that pushes new items
using &lt;code&gt;onNext&lt;/code&gt;, whenever there is demand downstream. Here&amp;#8217;s how the implementation might look like:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;class GameTickPublisherActor() extends Actor with ActorPublisher[TickDelta] {

  private var updateStack = List.empty[TickDelta]

  override def receive = {
    case delta: TickDelta =&amp;gt;
      updateStack :+= delta &lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;

      if (isActive &amp;amp;&amp;amp; totalDemand &amp;gt; 0) { &lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
        val (toTransmit, toPreserve) =
          updateStack.splitAt((updateStack.length - totalDemand.toInt).max(0))
        toTransmit.foreach(onNext) &lt;i class=&quot;conum&quot; data-value=&quot;3&quot;&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
        updateStack = toPreserve
      }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;colist arabic&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Adding to our element queue.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Checking whether we should push something downstream.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;3&quot;&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Transmitting &lt;code&gt;totalDemand&lt;/code&gt; elements to the upstream.&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;(Note that &lt;code&gt;onNext&lt;/code&gt;, &lt;code&gt;isActive&lt;/code&gt; and &lt;code&gt;totalDemand&lt;/code&gt; are all references introduced by the &lt;code&gt;ActorPublisher&lt;/code&gt; trait)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Then, all we need to do is fill out our &lt;code&gt;Source&lt;/code&gt; placeholder in &lt;code&gt;MainScreen&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;  val tickSource: Source[Nothing, ActorRef] = Source.actorPublisher(Props[GameTickPublisherActor])&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/mikolak-net/scala-stream-bullet-hell/tree/1b9e74efa6f4f9eb3331fdcc7c657971da93d4da&quot; target=&quot;_blank&quot;&gt;Here&amp;#8217;s the source code for this implementation&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;And we&amp;#8217;re getting our ticks:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;videoblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;video src=&quot;../../illustrations//stream-game//source-actorpublisher.mp4&quot; width=&quot;852&quot; autoplay loop&gt;
Your browser does not support the video tag.
&lt;/video&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;At first glance, it looks like it&amp;#8217;s working, although not completely smoothly. Let&amp;#8217;s try out the other approach.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;code_source_actorref_code&quot;&gt;&lt;code&gt;Source.actorRef&lt;/code&gt;&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This one&amp;#8217;s even simpler, all we have to do is set up the source:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;   val tickSource = Source.actorRef[TickDelta](0, OverflowStrategy.dropNew)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;actorRef&lt;/code&gt; requires two parameters:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;bufferSize&lt;/code&gt; - which we set to &lt;code&gt;0&lt;/code&gt; to disable the buffer,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;overflowStrategy&lt;/code&gt; - does not actually matter, since buffer is disabled - although &lt;code&gt;dropNew&lt;/code&gt; is basically the implementation&amp;#8217;s behavior in this case.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Here&amp;#8217;s how our tick demonstrator runs now:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;videoblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;video src=&quot;../../illustrations//stream-game//source-actorref.mp4&quot; width=&quot;852&quot; autoplay loop&gt;
Your browser does not support the video tag.
&lt;/video&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;And &lt;a href=&quot;https://github.com/mikolak-net/scala-stream-bullet-hell/tree/bca4fcd740c0b86af0a07c6e49611b3c67118ab4&quot; target=&quot;_blank&quot;&gt;here&amp;#8217;s the source code&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We can now plainly see that the &lt;code&gt;actorPublisher&lt;/code&gt; version fails to produce values expediently, compared to
the variant currently discussed. Why is this the case?&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;keeping_things_simple&quot;&gt;Keeping things simple&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Obviously there&amp;#8217;s a problem somewhere with the first implementation, but where?
Let&amp;#8217;s start with logging &lt;code&gt;toTransmit&lt;/code&gt; on every tick. It turns out we will get something like:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;List()
List()
List()
List()
List()
List(0.01748508)
List(0.016564002, 0.015894385)
List(0.017008292, 0.017062303, 0.015971143)
List(0.01700024, 0.01696568, 0.016022582, 0.01697306)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This is obviously incorrect, since we should always check for a positive demand (and therefore
should be sending non-empty lists).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Of course, this implies we have the third member of the unholy duo of programming mistakes, namely
an off-by-one error. Specifically, here:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;          updateStack.splitAt((updateStack.length - totalDemand.toInt).max(0))&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;we &lt;code&gt;max&lt;/code&gt; to &lt;code&gt;0&lt;/code&gt; instead of &lt;code&gt;1&lt;/code&gt;. Correcting the line to &lt;code&gt;max(1)&lt;/code&gt; will make both implementations run smoothly.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Moreover, seeing as how the &lt;code&gt;actorPublisher&lt;/code&gt; implementation has no advantage over
the &lt;code&gt;actorRef&lt;/code&gt; one, and is substantially more convoluted, we&amp;#8217;ll be sticking with the latter.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;up_next&quot;&gt;Up next&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We now have a tick source, so we will be able to liven our game&amp;#8217;s world
a little by adding some entities mulling about.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Also, we have completely neglected thread safety up until now - we&amp;#8217;ve been
updating the game state (in this case, the &lt;code&gt;tick&lt;/code&gt; var) from at least
two different threads. We&amp;#8217;ll be working towards eliminating this kludge as well.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Stay tuned!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_1&quot;&gt;
&lt;a href=&quot;#_footnoteref_1&quot;&gt;1&lt;/a&gt;. Confusingly, running a graph is &lt;strong&gt;also&lt;/strong&gt; called &quot;materializing&quot; it&amp;#8230;&amp;#8203;
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_2&quot;&gt;
&lt;a href=&quot;#_footnoteref_2&quot;&gt;2&lt;/a&gt;. You can do either, or both at the same time - consult &lt;a href=&quot;http://doc.akka.io/docs/akka/current/scala/stream/stream-composition.html#materialized-values&quot; target=&quot;_blank&quot;&gt;the documentation&lt;/a&gt; for details.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_3&quot;&gt;
&lt;a href=&quot;#_footnoteref_3&quot;&gt;3&lt;/a&gt;. This is actually incorrect, but the error is not critical for our purposes.
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Reactive Streams in Scala meet a Game Engine - Part 3</title>
      <link>https://xn--mikoak-6db.net/blog/2017/akka-streams-libgdx-3.html</link>
      <pubDate>Wed, 5 Jul 2017 00:00:00 +0200</pubDate>
      <guid isPermaLink="false">blog/2017/akka-streams-libgdx-3.html</guid>
      	<description>
	&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;series_table_of_contents&quot;&gt;Series&apos; Table of Contents&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2016/akka-streams-libgdx-1.html&quot;&gt;Part 1 - Introduction&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2016/akka-streams-libgdx-2.html&quot;&gt;Part 2 - Defining Requirements&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2017/akka-streams-libgdx-3.html&quot;&gt;Part 3 - Setting Up the Project&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2017/akka-streams-libgdx-4.html&quot;&gt;Part 4 - Coding the Tick Source&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2017/akka-streams-libgdx-5.html&quot;&gt;Part 5 - Making the Game Live&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2017/akka-streams-libgdx-6.html&quot;&gt;Part 6 - (De)Composition is key&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2017/akka-streams-libgdx-7.html&quot;&gt;Part 7 - Finally, interactivity!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;in_this_episode&quot;&gt;In this episode&amp;#8230;&amp;#8203;&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&amp;#8230;&amp;#8203;we will start the actual development of the game by planting some boilerplate, taking a crash course
in libgdx, and setting the stage for the actual streaming implementation.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;libgdx_basics&quot;&gt;Libgdx Basics&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;project_structure&quot;&gt;Project Structure&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://libgdx.badlogicgames.com/&quot; target=&quot;_blank&quot;&gt;Libgdx&lt;/a&gt;, as mentioned previously, is a popular JVM framework for game design. Rudimentary
Scala support exists - in fact, our game skeleton is based on &lt;a href=&quot;https://github.com/ajhager/libgdx-sbt-project.g8&quot; target=&quot;_blank&quot;&gt;ajhager&amp;#8217;s g8 template&lt;/a&gt;,
or rather, &lt;a href=&quot;https://github.com/Darkyenus/libgdx-sbt-project.g8&quot; target=&quot;_blank&quot;&gt;its fork by Darkyenus&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Because libgdx is a multi-platform engine, projects using it are always multi-module,
split into:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;a &lt;code&gt;core&lt;/code&gt; module that contains the generic base of the game,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;one or modules, each for a different target&amp;#8217;s specific code, e.g. &lt;code&gt;desktop&lt;/code&gt;, &lt;code&gt;android&lt;/code&gt;, &lt;code&gt;ios&lt;/code&gt;, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In our case we will be only implementing a desktop version, so we will have two modules:
&lt;code&gt;core&lt;/code&gt;, and &lt;code&gt;desktop&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;boilerplate_classes&quot;&gt;Boilerplate classes&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The &lt;code&gt;core&lt;/code&gt; &apos;s structures starts with the primary entrypoint class - a subclass of &lt;code&gt;Game&lt;/code&gt;. Each platform
variant creates an instance of this class during initialization.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In turn a &lt;code&gt;Game&lt;/code&gt; operates on a number of &lt;code&gt;Screen&lt;/code&gt; s, exactly one of each
is always active. The &lt;code&gt;Screen&lt;/code&gt; subclasses usually contain most of the game&amp;#8217;s
logic, the &lt;code&gt;Game&lt;/code&gt; subclass should only provide minimal global controller &quot;glue&quot;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Both &lt;code&gt;Game&lt;/code&gt; and &lt;code&gt;Screen&lt;/code&gt; are coded in by implementing the provided lifecycle methods,
&lt;code&gt;create&lt;/code&gt;, &lt;code&gt;pause&lt;/code&gt;, &lt;code&gt;resume&lt;/code&gt;, &lt;code&gt;dispose&lt;/code&gt; etc. &lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_1&quot; class=&quot;footnote&quot; href=&quot;#_footnote_1&quot; title=&quot;View footnote.&quot;&gt;1&lt;/a&gt;]&lt;/sup&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;With the general introduction out of the way, let&amp;#8217;s see how a minimal game
looks like. Here&amp;#8217;s the &lt;code&gt;core&lt;/code&gt; logic:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;package net.mikolak.stream_bullethell

import com.badlogic.gdx.graphics.GL20
import com.badlogic.gdx.{Game, Gdx, ScreenAdapter}

class BulletHell extends Game {

  override def create(): Unit = { &lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
    setScreen(new MainScreen) &lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
  }

}

class MainScreen extends ScreenAdapter { &lt;i class=&quot;conum&quot; data-value=&quot;3&quot;&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;

  override def render(delta: Float) = {
    Gdx.gl.glClearColor(0, 0, 0.5f, 1) &lt;i class=&quot;conum&quot; data-value=&quot;4&quot;&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
  }

}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;colist arabic&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Overriding a lifecycle method here.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Setting the active screen to our implementation.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;3&quot;&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Using an abstract &lt;em&gt;Adapter&lt;/em&gt; class instead of the actual &lt;code&gt;Screen&lt;/code&gt; interface for convenience.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;4&quot;&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Painting the background color to dark blue.&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We then define a platform specific implementation, in this case,
a &quot;desktop&quot;&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_2&quot; class=&quot;footnote&quot; href=&quot;#_footnote_2&quot; title=&quot;View footnote.&quot;&gt;2&lt;/a&gt;]&lt;/sup&gt; JRE target:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;package net.mikolak.stream_bullethell

import com.badlogic.gdx.backends.lwjgl.{LwjglApplication, LwjglApplicationConfiguration} &lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;

object Main extends App {
    val cfg = new LwjglApplicationConfiguration
    cfg.title = &quot;stream-bullethell-demo&quot;
    cfg.height = 480
    cfg.width = 800
    cfg.forceExit = false
    new LwjglApplication(new BulletHell, cfg) &lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;colist arabic&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&quot;Desktop&quot;-specific imports.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Pointing to our &lt;code&gt;Game&lt;/code&gt; subclass.&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This leaves us with an 800px by 480px screen with a dark blue background.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;erecting_a_base_camp&quot;&gt;Erecting a base camp&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The previous listing doesn&amp;#8217;t leave us much room for experiment, so let&amp;#8217;s
spice things up a little by adding a proper Hello World message to &lt;code&gt;MainScreen&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;class MainScreen extends ScreenAdapter {

  private lazy val camera = new OrthographicCamera() &lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
  private val batch: SpriteBatch = new SpriteBatch() &lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;

  private lazy val font = {
    val f = new BitmapFont()
    f.getData.setScale(2f)
    f
  } &lt;i class=&quot;conum&quot; data-value=&quot;3&quot;&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;

  override def show() = {
    camera.setToOrtho(false, 800, 480) &lt;i class=&quot;conum&quot; data-value=&quot;4&quot;&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
  }

  override def render(delta: Float) = {
    Gdx.gl.glClearColor(0, 0, 0.5f, 1)
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
    camera.update()
    batch.setProjectionMatrix(camera.combined) &lt;i class=&quot;conum&quot; data-value=&quot;5&quot;&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
    batch.begin() &lt;i class=&quot;conum&quot; data-value=&quot;6&quot;&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
    font.draw(batch, &quot;Hello libgdx!&quot;, 0, font.getCapHeight) &lt;i class=&quot;conum&quot; data-value=&quot;7&quot;&gt;&lt;/i&gt;&lt;b&gt;(7)&lt;/b&gt;
    batch.end()
  }

}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;colist arabic&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The camera defines how the player &quot;looks&quot; at the object on the scene.
In this case, we are using an Orthographic camera, which means we&amp;#8217;re looking
at the scene &quot;straight down&quot; (representing a 2D game - libgdx is also capable of 3D).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;A &lt;code&gt;SpriteBatch&lt;/code&gt; lets us draw multiple objects in one pass. Due to how actual
games have a lot of objects on screen, using a &lt;code&gt;SpriteBatch&lt;/code&gt; instance is the standard way
of presenting &lt;em&gt;anything&lt;/em&gt; on screen in libgdx.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;3&quot;&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Fonts, like many other resources, are explicitly declared for efficiency&amp;#8217;s sake. This
will give us a &quot;default&quot; font.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;4&quot;&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Once the screen becomes visible, the camera is set to show a top-down view
of a rectangle of &lt;code&gt;[0, 0, 800, 480]&lt;/code&gt; - since this is the same area as the &quot;desktop&quot;
target&amp;#8217;s window, each scene point corresponds to a single pixel.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;5&quot;&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;SpriteBatch&lt;/code&gt; is ordered to present the same viewport as the camera.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;6&quot;&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;A batch must be always explicitly begun and ended. Objects are queued for drawing with it in between
those two points.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;7&quot;&gt;&lt;/i&gt;&lt;b&gt;7&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Here&amp;#8217;s where we actually draw the text. The X coordinate corresponds to the far-left
side of the screen, and the Y coordinate corresponds to the bottom of the screen &lt;em&gt;plus&lt;/em&gt; the capital-character height
of glyphs of our font.&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This nets us an actual Hello World, but we still have nothing to work on.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;tick_tick_tick_tick&quot;&gt;Tick tick tick tick&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;code&gt;Screen&lt;/code&gt; s in libgdx are updated once per an interval called a &lt;em&gt;tick&lt;/em&gt;. For each tick,
the &lt;code&gt;render&lt;/code&gt; method is called, and only once it&amp;#8217;s complete can another tick occur&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_3&quot; class=&quot;footnote&quot; href=&quot;#_footnote_3&quot; title=&quot;View footnote.&quot;&gt;3&lt;/a&gt;]&lt;/sup&gt;.
This means all your game&amp;#8217;s logic other than (de)initialization should be called from &lt;code&gt;render&lt;/code&gt;, and - ideally - run in the same thread.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Due to machine-specific issues or issues with the game logic itself &lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_4&quot; class=&quot;footnote&quot; href=&quot;#_footnote_4&quot; title=&quot;View footnote.&quot;&gt;4&lt;/a&gt;]&lt;/sup&gt;, the length of time
each tick can take is not constant - this is why the &lt;code&gt;delta&lt;/code&gt; parameter is provided, giving the millisecond amount between current an
previous ticks.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As you have probably figured out by now, we&amp;#8217;re going to need to tap into the tick mechanism to get started. So, let&amp;#8217;s end this installment
by printing up subsequent ticks. Here&amp;#8217;s how our &lt;code&gt;MainScreen&lt;/code&gt; will look now:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;class MainScreen extends ScreenAdapter {

  lazy val camera = new OrthographicCamera()
  val batch: SpriteBatch = new SpriteBatch()

  var tick = 1L

  lazy val font = {
    val f = new BitmapFont()
    f.getData.setScale(2f)
    f
  }

  override def render(delta: Float) = {
    tick += 1 &lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;

    //print tick
    Gdx.gl.glClearColor(0, 0, 0.5f, 1)
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
    camera.update()
    batch.setProjectionMatrix(camera.combined)
    batch.begin()
    font.draw(batch, s&quot;Tick: $tick&quot;, 0, font.getCapHeight) &lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
    batch.end()
  }

  override def show() = {
    camera.setToOrtho(false, 800, 480)
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;colist arabic&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Setting the tick number&amp;#8230;&amp;#8203;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&amp;#8230;&amp;#8203;and printing it out.&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;You can grab the source code of the entire project in this state &lt;a href=&quot;https://github.com/mikolak-net/scala-stream-bullet-hell/tree/6e0b52470bff5ad884e97a955104a70a65f887df&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.
Libgdx itself has very comprehehsive documentation, so if you wish to find out more about it,
&lt;a href=&quot;https://github.com/libgdx/libgdx/wiki&quot; target=&quot;_blank&quot;&gt;its wiki is a good place to start&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;next_up&quot;&gt;Next up&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Now we finally have a starting point for actual development of our concept.
In the next installment, which will follow very shortly, we&amp;#8217;ll get on to do that - specifically, by creating a simple
stream that is able to tap into the tick mechanism of libgdx&amp;#8217;s engine.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_1&quot;&gt;
&lt;a href=&quot;#_footnoteref_1&quot;&gt;1&lt;/a&gt;. This factory-method-based approach to lifecycle management is quite widespread, and used most prominently in the JVM world by the Android ecosystem
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_2&quot;&gt;
&lt;a href=&quot;#_footnoteref_2&quot;&gt;2&lt;/a&gt;. In the &quot;personal computer&quot; sense: anything running on an i386/amd64 architecture or similar.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_3&quot;&gt;
&lt;a href=&quot;#_footnoteref_3&quot;&gt;3&lt;/a&gt;. In other words, the &lt;code&gt;render&lt;/code&gt; method is guaranteed to be called at most once simultaneously - similarly to how &lt;code&gt;receive&lt;/code&gt; works for Akka actors. This is also how most JVM GUI engines operate.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_4&quot;&gt;
&lt;a href=&quot;#_footnoteref_4&quot;&gt;4&lt;/a&gt;. As corollary to the above, sluggish game logic in the &lt;code&gt;render&lt;/code&gt; implementation causes the subsequent call to be delayed.
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Pomisos released!</title>
      <link>https://xn--mikoak-6db.net/blog/2017/pomisos-release.html</link>
      <pubDate>Tue, 28 Mar 2017 00:00:00 +0200</pubDate>
      <guid isPermaLink="false">blog/2017/pomisos-release.html</guid>
      	<description>
	&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;what&quot;&gt;What?&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As I alluded to &lt;a href=&quot;https://mikołak.net/blog/2016/akka-streams-libgx-2.html&quot;&gt;earlier&lt;/a&gt;, my free time (or
at least the portion dedicated to coding) was recently taken up by a specific project. That project
has been finally released: say hello to &lt;a href=&quot;https://github.com/mikolak-net/pomisos&quot;&gt;pomisos&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Pomisos is a desktop productivity app that helps users with elements of the Pomodoro Technique, specifically
automating distraction removal (e.g. e-mail clients). There&amp;#8217;s more info on the &lt;a href=&quot;https://github.com/mikolak-net/pomisos#what-is-it&quot;&gt;project page&lt;/a&gt;,
what I&amp;#8217;m going to concentrate on here are several thoughts that came up during the app&amp;#8217;s development.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;javafx_is_was_surprisingly_ahead_of_its_time&quot;&gt;JavaFX is/was surprisingly ahead of its time&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;While the previous &quot;official&quot; Java desktop GUI framework, Swing, was based on the Publisher-Observer pattern,
 JavaFX is instead oriented around &lt;a href=&quot;https://docs.oracle.com/javase/8/javafx/api/javafx/beans/property/Property.html&quot;&gt;Properties&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Although superficially similar (they also allow listeners to be added), the crucial difference is the concept
of &quot;binding&quot; properties to one another, essentially synchronizing them automatically. This allows for
 essentially writing GUI logic like a set of logic rules, e.g. (pseudocode) :&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;managePane.visible.bind(option.selected)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;where &lt;code&gt;visible&lt;/code&gt; and &lt;code&gt;selected&lt;/code&gt; are both of type &lt;code&gt;BooleanProperty&lt;/code&gt;. What&amp;#8217;s more, you can bind multiple properties into complex statements, which essentially allows you to
create dataflows that are updated automatically.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Essentially, the logic starts to look like a declarative
stream specification, much more maintainable and convenient than the &quot;Listener Hell&quot; of Swing.
It&amp;#8217;s a shame JavaFX arrived only when classic desktop apps have already started to go out of fashion.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;there_s_comprehensive_support_for_javafx_in_scala&quot;&gt;There&amp;#8217;s comprehensive support for JavaFX in Scala&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;If you do want to start developing JavaFX applications, however, there&amp;#8217;s several libraries that allow to comfortably code using Scala.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The first one, of course, is &lt;a href=&quot;http://www.scalafx.org/&quot;&gt;scalafx&lt;/a&gt;, which simply &quot;Scalifies&quot; the API, and adds a convenient DSL for property bindings.
The previous example would now look like:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;managePane.visible &amp;lt;== option.selected&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Even more rule-like&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_1&quot; class=&quot;footnote&quot; href=&quot;#_footnote_1&quot; title=&quot;View footnote.&quot;&gt;1&lt;/a&gt;]&lt;/sup&gt;!&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Another thing is FXML development. FXML works pretty much like e.g. Android&amp;#8217;s UI XML files - it allows you to separate
general UI specification (the XML) from your business-logic code (a &lt;code&gt;Controller&lt;/code&gt; class).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;To take advantage of that convenience in Scala, there&amp;#8217;s &lt;a href=&quot;https://github.com/vigoo/scalafxml&quot;&gt;scalafxml&lt;/a&gt;. What&amp;#8217;s especially nice is that integrates Scala
DI approaches (like Macwire), to automatically instantiate the &lt;code&gt;Controller&lt;/code&gt; classes&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_2&quot; class=&quot;footnote&quot; href=&quot;#_footnote_2&quot; title=&quot;View footnote.&quot;&gt;2&lt;/a&gt;]&lt;/sup&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;gremlin_based_graph_dbs_are_too_involved_for_simple_projects&quot;&gt;Gremlin-based Graph DBs are too involved for simple projects&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Over the last year or so, I&amp;#8217;ve been evaluating &lt;a href=&quot;https://tinkerpop.apache.org/&quot;&gt;Gremlin/TinkerPop&lt;/a&gt;, specifically the
&lt;a href=&quot;https://github.com/mpollmeier/gremlin-scala&quot;&gt;Scala implementation&lt;/a&gt;, in a variety of projects. Out of curiosity, I went with it as the DB layer for pomisos,
using an embedded OrientDB instance as the storage implementation.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Unfortunately&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_3&quot; class=&quot;footnote&quot; href=&quot;#_footnote_3&quot; title=&quot;View footnote.&quot;&gt;3&lt;/a&gt;]&lt;/sup&gt;, while the API Gremlin API is almost amazingly intuitive (and the
Scala implementation doubly so, big kudos to &lt;a href=&quot;https://github.com/mpollmeier&quot;&gt;Michael Pollmeier&lt;/a&gt; for his work here), I found the
&quot;constant&quot; cost of introducing this kind of abstraction seems to be an overkill for projects with only several, unconected,
entity types.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Specifically, the biggest problem was creating generalized DAOs, and attempting to use edges as an &lt;em&gt;Ersatz&lt;/em&gt; for
 trivial relations.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;To be clear though: in this case, the aforementioned cost of introduction is comparable to an RDBMs. It&amp;#8217;s just that, for projects
 of scale comparable to pomisos, simple object stores are more than sufficient.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;relaxed_approach_to_development_is_the_way_to_go_when_prototyping&quot;&gt;Relaxed approach to development is the way to go when prototyping&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;From my observations, it appears there&amp;#8217;s a persistent myth that experienced developers somehow create perfect code instantly - like some
sort of rainbow-excreting unicorns. I find that exceedingly harmful, since it effectively reduces the amount of code that gets out to the public,
due to lowered self-esteem of aspiring devs.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;By the way, one of the best things in &lt;em&gt;Clean Code&lt;/em&gt; is the part where Uncle Bob shows the continuous process of implementing a simple example, starting
 from a very messy prototype.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This is exactly why I haven&amp;#8217;t squashed the ongoing commits. I think it&amp;#8217;s a good idea for there to be more transparency in development
of similar-scale projects, exactly because of the learning potential in demonstrating the actual coding process.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;coming_up&quot;&gt;Coming up&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;That&amp;#8217;s all regarding the development of &lt;a href=&quot;https://github.com/mikolak-net/pomisos&quot;&gt;pomisos&lt;/a&gt;. Check out the project if you&amp;#8217;re interested.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Next time, we&amp;#8217;ll go back to streaming and game development!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_1&quot;&gt;
&lt;a href=&quot;#_footnoteref_1&quot;&gt;1&lt;/a&gt;. Although, to be honest, I&amp;#8217;m not a fan of the &lt;a href=&quot;http://www.scalafx.org/docs/properties/&quot;&gt;other parts of the DSL&lt;/a&gt;, as I find &quot;plain&quot; &lt;code&gt;map&lt;/code&gt;/&lt;code&gt;flatMap&lt;/code&gt; combinators would be a much better fit here, instead of a tailor-made DSL.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_2&quot;&gt;
&lt;a href=&quot;#_footnoteref_2&quot;&gt;2&lt;/a&gt;. There&amp;#8217;s one problem - the DI for those classes is handled at runtime.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_3&quot;&gt;
&lt;a href=&quot;#_footnoteref_3&quot;&gt;3&lt;/a&gt;. &amp;#8230;&amp;#8203;and as I expected&amp;#8230;&amp;#8203;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Reactive Streams in Scala meet a Game Engine - Part 2</title>
      <link>https://xn--mikoak-6db.net/blog/2016/akka-streams-libgdx-2.html</link>
      <pubDate>Wed, 16 Nov 2016 00:00:00 +0100</pubDate>
      <guid isPermaLink="false">blog/2016/akka-streams-libgdx-2.html</guid>
      	<description>
	&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;series_table_of_contents&quot;&gt;Series&apos; Table of Contents&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2016/akka-streams-libgdx-1.html&quot;&gt;Part 1 - Introduction&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2016/akka-streams-libgdx-2.html&quot;&gt;Part 2 - Defining Requirements&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2017/akka-streams-libgdx-3.html&quot;&gt;Part 3 - Setting Up the Project&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2017/akka-streams-libgdx-4.html&quot;&gt;Part 4 - Coding the Tick Source&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2017/akka-streams-libgdx-5.html&quot;&gt;Part 5 - Making the Game Live&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2017/akka-streams-libgdx-6.html&quot;&gt;Part 6 - (De)Composition is key&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2017/akka-streams-libgdx-7.html&quot;&gt;Part 7 - Finally, interactivity!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Unfortunately, just after &lt;a href=&quot;http://mikołak.net/blog/2016/akka-streams-libgx-1.html&quot;&gt;the first installment of this series&lt;/a&gt;, I&amp;#8217;ve involved myself in another side-project, which will probably be released soon. But hey, that&amp;#8217;s no excuse to at least make a small update in the meantime, to keep the ball rolling, right?&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;scope&quot;&gt;Scope&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;desiderata&quot;&gt;Desiderata&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As mentioned in the first post, the next step should be defining our scope. This basically boils down to making our exploration of the streaming paradigm as efficient as practicable, specifically identifying problems intrinsic to game programming.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;One of those is efficient resource allocation. Games need to spawn and remove potentially large numbers of object quickly, and without taxing the memory of the device. For JVM-made games, Android device limits would probably serve as the most prominent example in recent years&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_1&quot; class=&quot;footnote&quot; href=&quot;#_footnote_1&quot; title=&quot;View footnote.&quot;&gt;1&lt;/a&gt;]&lt;/sup&gt;. Game engines like libgdx mitigate this problem by employing pools of mutable data structures (which reduces the number of created objects, and hence GC time), among other tricks.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Another issue is smooth rendering of the game&amp;#8217;s updates. Enjoyment drops sharply if the game becomes visibly choppy.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;A related problem is responsiveness to player input. Smooth FPS gives little consolation if the player remains constantly frustrated while unable to react in time to the unfolding events.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;To sum up&amp;#8230;&amp;#8203;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We would like to have a game that:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;requires relatively quick player reaction (so e.g. no turn-based games),&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;has a lot of events happening at any given moment,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;and employs a large number of entities to do so.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Recall also one of our initial assumptions: mistakes will be made prominent (and prominently made), including potentially avoidable ones, in order to showcase them and their solutions.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;first_sketches&quot;&gt;First sketches&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Obviously, we want some kind of an arcade game, somewhat close in style to a &lt;a href=&quot;http://tvtropes.org/pmwiki/pmwiki.php/Main/ShootEmUp&quot;&gt;shoot&amp;#8217;em up&lt;/a&gt;, even drifting into &lt;a href=&quot;http://tvtropes.org/pmwiki/pmwiki.php/Main/BulletHell&quot;&gt;bullet hell&lt;/a&gt; territory&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_2&quot; class=&quot;footnote&quot; href=&quot;#_footnote_2&quot; title=&quot;View footnote.&quot;&gt;2&lt;/a&gt;]&lt;/sup&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;For starters, we want some sort of controllable character, multiple, respawning enemies, and some sort of score meter (which will help us gauge the rate of updates).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Here&amp;#8217;s a veeery rudimentary sketch of how the game will look like at the beginning:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock thumb&quot; style=&quot;text-align: center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//stream-game//starter-sketch.png&quot; alt=&quot;starter sketch&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Figure 1. No, I&amp;#8217;m not a graphic artist, why do you ask?&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Of course, we&amp;#8217;ll keep adding stuff as we go along, but we already have more than enough work to do.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;up_next&quot;&gt;Up Next&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In the following episode we&amp;#8217;ll finally see some code. We&amp;#8217;re going to define the boilerplate skeleton of our application, and create an Akka Streams source that allows to react to game state updates.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_1&quot;&gt;
&lt;a href=&quot;#_footnoteref_1&quot;&gt;1&lt;/a&gt;. Alongside Minecraft&amp;#8217;s &lt;a href=&quot;http://minecraft.gamepedia.com/Chunk&quot;&gt;chunk&lt;/a&gt; management.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_2&quot;&gt;
&lt;a href=&quot;#_footnoteref_2&quot;&gt;2&lt;/a&gt;. Apologies for &lt;a href=&quot;http://tvtropes.org/pmwiki/pmwiki.php/main/tvtropeswillruinyourlife&quot;&gt;linking to TV Tropes&lt;/a&gt;.
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Reactive Streams in Scala meet a Game Engine - Part 1</title>
      <link>https://xn--mikoak-6db.net/blog/2016/akka-streams-libgdx-1.html</link>
      <pubDate>Mon, 12 Sep 2016 00:00:00 +0200</pubDate>
      <guid isPermaLink="false">blog/2016/akka-streams-libgdx-1.html</guid>
      	<description>
	&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;series_table_of_contents&quot;&gt;Series&apos; Table of Contents&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2016/akka-streams-libgdx-1.html&quot;&gt;Part 1 - Introduction&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2016/akka-streams-libgdx-2.html&quot;&gt;Part 2 - Defining Requirements&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2017/akka-streams-libgdx-3.html&quot;&gt;Part 3 - Setting Up the Project&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2017/akka-streams-libgdx-4.html&quot;&gt;Part 4 - Coding the Tick Source&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2017/akka-streams-libgdx-5.html&quot;&gt;Part 5 - Making the Game Live&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2017/akka-streams-libgdx-6.html&quot;&gt;Part 6 - (De)Composition is key&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/blog/2017/akka-streams-libgdx-7.html&quot;&gt;Part 7 - Finally, interactivity!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;intro&quot;&gt;Intro&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;It&amp;#8217;s always interesting to see a new (or newly fashionable) technique being applied to new and unexpected areas, and
see whether it makes sense (and what makes or breaks it).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Such is the case with Scala (reactive) streaming and computer game creation. A while ago, I stumbled upon
&lt;a href=&quot;https://www.youtube.com/watch?v=B72a1D2xvFs&quot;&gt;a talk by Aleksandar Prokopec&lt;/a&gt;, which planted the seed of inspiration for
this blog series. And what set that seed on a path of growth was another presentation, this time by
&lt;a href=&quot;http://2016.geecon.org/speakers/info.html?id=131&quot;&gt;Michał Płachta&lt;/a&gt;
&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_1&quot; class=&quot;footnote&quot; href=&quot;#_footnote_1&quot; title=&quot;View footnote.&quot;&gt;1&lt;/a&gt;]&lt;/sup&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;so_why_another_thing_on_this&quot;&gt;So, why another thing on this?&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Well, of course it wouldn&amp;#8217;t be fun if the blog series was just a same-scenario rehash. Here&amp;#8217;s what&amp;#8217;s going to be different - while
 the aforementioned talks were more-or-less &quot;from-scratch&quot; affairs, creating a game engine and/or a streaming platform
 along the way - I will be going the &quot;lazy&quot; route, and attempting to join together a ready-made game engine with
 a ready-made streaming framework.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The primary advantage of this kind-of-unique&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_2&quot; class=&quot;footnote&quot; href=&quot;#_footnote_2&quot; title=&quot;View footnote.&quot;&gt;2&lt;/a&gt;]&lt;/sup&gt; setup is that the focus almost
by definition falls on the paradigm mismatch problem, and thus it&amp;#8217;s easy to see what one must solve to make
the whole shebang work.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;And there&amp;#8217;s a related blessing in disguise - &lt;em&gt;because&lt;/em&gt; we&amp;#8217;re operating between two rigid boundaries of
stable libraries, any domain-related idiosyncrasies will be easily identifiable, and have a reduced
chance of rising up later on to bite is in the hind parts.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Now, let&amp;#8217;s talk framework choices.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;decisions_decisions&quot;&gt;Decisions, decisions&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;game_engine&quot;&gt;Game Engine&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;It may come as a surprise to some of the readers, but JVM game programming is not exactly a barren
 field, despite the infamous &quot;Java is slow&quot; stereotype. Probably the greatest influence in recent years
 has been the rise of Android, hence why a high proportion of JVM game engines provide support for both
 &quot;mobile&quot; and &quot;desktop&quot; targets&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_3&quot; class=&quot;footnote&quot; href=&quot;#_footnote_3&quot; title=&quot;View footnote.&quot;&gt;3&lt;/a&gt;]&lt;/sup&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In any case, two engines seem to be the strongest contenders at the moment: &lt;a href=&quot;http://libgdx.badlogicgames.com/&quot;&gt;libGDX&lt;/a&gt;
and &lt;a href=&quot;http://jmonkeyengine.org/&quot;&gt;jMonkeyEngine&lt;/a&gt;. Both have been present on the scene for several years, and offer
comprehensive game development feature sets&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_4&quot; class=&quot;footnote&quot; href=&quot;#_footnote_4&quot; title=&quot;View footnote.&quot;&gt;4&lt;/a&gt;]&lt;/sup&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The choice, however, falls on libGDX, for two reasons: it has explicit support for 2D rendering, and
it&amp;#8217;s the one I&amp;#8217;m actually familiar with.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;I&amp;#8217;m going to expand on libGDX in upcoming installments. Right now, let&amp;#8217;s roll over to the other side for the&amp;#8230;&amp;#8203;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;streaming_implementation&quot;&gt;Streaming Implementation&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Apart from the obvious choice of Akka Streams, I was also monitoring the progress of &lt;a href=&quot;https://github.com/sirthias/swave/&quot;&gt;Swave&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;I recommend following its development closely. It is, in implementation and design, significantly less complex than Akka Streams.
 Swave can therefore serve as a convenient stepping stone in beating the learning curve, when adapting to the streaming paradigm.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;However, Swave is still shaping up, its documentation is somewhat lacking&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_5&quot; class=&quot;footnote&quot; href=&quot;#_footnote_5&quot; title=&quot;View footnote.&quot;&gt;5&lt;/a&gt;]&lt;/sup&gt;, and I
have a sneaking suspicion that some unique features of its &quot;competitor&quot; (like blueprinting) might come in handy, so we&amp;#8217;re
sticking with Akka Streams.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;OK, so the tech base is decided now. All that remains is one tiny detail - just what is the end goal and how to achieve it?&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;some_ground_rules&quot;&gt;Some ground rules&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Let&amp;#8217;s start with the goal itself, since it&amp;#8217;s pretty obvious - a natural way to verify whether something works (or doesn&amp;#8217;t),
is through a practical proof-of-concept. Therefore, during this series, I&amp;#8217;m going to work towards a fully-playable simple game,
that uses streaming as its core operational model.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This leads nicely to the preferred form of the series, i.e. a development blog - meaning:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;installments will be posted (hopefully) regularly,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;with a soft word-length cutoff,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;mistakes will be made prominent (and prominently made), including potentially avoidable ones, in
order to showcase them and their solutions&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_6&quot; class=&quot;footnote&quot; href=&quot;#_footnote_6&quot; title=&quot;View footnote.&quot;&gt;6&lt;/a&gt;]&lt;/sup&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Finally, to spice things up a bit, I&amp;#8217;m going to deliberately forgo gathering &quot;technical&quot; inspiration from the
aforementioned related talks and projects (like the additional abstractions introduced by Dr. Prokopec), in order to see
where the problem domain lends toward convergent solution creation.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;up_next&quot;&gt;Up Next&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In &lt;a href=&quot;http://mikołak.net/blog/2016/akka-streams-libgx-2.html&quot;&gt;the next installment&lt;/a&gt;, we&amp;#8217;re going to cover defining the scope of the PoC game. Stay tuned!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_1&quot;&gt;
&lt;a href=&quot;#_footnoteref_1&quot;&gt;1&lt;/a&gt;. Source code &lt;a href=&quot;https://github.com/miciek/snake-multiplayer-akka-streams&quot;&gt;here&lt;/a&gt;.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_2&quot;&gt;
&lt;a href=&quot;#_footnoteref_2&quot;&gt;2&lt;/a&gt;. There are at least two similar projects. One went in the direction of &lt;a href=&quot;https://github.com/jmhofer/activator-libgdx-rxscala&quot;&gt;providing&lt;/a&gt; an Rx* translation layer. It seems to be inactive for about two years now. &lt;a href=&quot;https://bitbucket.org/fogone/rxgdx/overview&quot;&gt;Another&lt;/a&gt;, implemented in Kotlin, seems to be under active development. Both use the Rx* family of libraries.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_3&quot;&gt;
&lt;a href=&quot;#_footnoteref_3&quot;&gt;3&lt;/a&gt;. Be aware &apos;though that since Android supports &quot;native applications&quot;,  a lot of Android games are written in C/C++ instead.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_4&quot;&gt;
&lt;a href=&quot;#_footnoteref_4&quot;&gt;4&lt;/a&gt;. To answer a - perhaps - lingering question: Minecraft, the poster child of JVM games, uses neither. It has a proprietary engine, with several native bridges for OpenGL support etc.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_5&quot;&gt;
&lt;a href=&quot;#_footnoteref_5&quot;&gt;5&lt;/a&gt;. Do see &lt;a href=&quot;https://github.com/sirthias/swave#resources&quot;&gt;the presentations&lt;/a&gt; &apos;though for a good feel of what it offers.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_6&quot;&gt;
&lt;a href=&quot;#_footnoteref_6&quot;&gt;6&lt;/a&gt;. Also making for a nice excuse in case of eggregious blunders.
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Painless, type-safe config file parsing with Macwire and Ficus</title>
      <link>https://xn--mikoak-6db.net/blog/2016/type-safe-config-ficus-macwire.html</link>
      <pubDate>Sat, 30 Apr 2016 00:00:00 +0200</pubDate>
      <guid isPermaLink="false">blog/2016/type-safe-config-ficus-macwire.html</guid>
      	<description>
	&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;intro&quot;&gt;Intro&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Scala has a distinct advantage of an &quot;official&quot; configuration file format, i.e. &lt;a href=&quot;https://github.com/typesafehub/config/blob/master/HOCON.md&quot;&gt;HOCON&lt;/a&gt;.
Even better, the niceness transcends the officialdom - HOCON is quite well-defined, and it has lots of useful of features. In other words, it&amp;#8217;s
 simply a &lt;strong&gt;good&lt;/strong&gt; format.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Of course, any Scala developer past their Day 3 can tell you of a certain little quirk - the
&lt;a href=&quot;https://github.com/typesafehub/config&quot;&gt;reference parser implementation&lt;/a&gt; is a &lt;em&gt;Java&lt;/em&gt; library.
Even though Java-Scala interop is actually mostly tolerable once one learns the ropes, it still remains slightly annoying.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This post will describe a possible approach for reducing that annoyance by synergizing two well-known libs.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;admonitionblock note&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td class=&quot;icon&quot;&gt;
&lt;i class=&quot;fa icon-note&quot; title=&quot;Note&quot;&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class=&quot;content&quot;&gt;
The following is intended for devs already minimally familiar with Macwire. If you&amp;#8217;re not, take
&lt;a href=&quot;https://github.com/adamw/macwire&quot;&gt;a look at the README&lt;/a&gt;. Reading the &lt;a href=&quot;https://github.com/adamw/macwire#macwire&quot;&gt;the Introduction&lt;/a&gt; will
suffice.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;config&quot;&gt;Config&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;There are several popular pure-Scala HOCON parsing libraries available. We&amp;#8217;re going to use &lt;a href=&quot;https://github.com/iheartradio/ficus&quot;&gt;Ficus&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As a library, it&amp;#8217;s extremely straightforward - specify the config file, the subpath within it, the type you want
it to be parsed into - the lib will do the rest. It even supports extraction into
&lt;a href=&quot;https://github.com/iheartradio/ficus#supported-types&quot;&gt;semi-arbitrary data types&lt;/a&gt;, like so:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-hocon&quot; data-lang=&quot;hocon&quot;&gt;server {
    host: &quot;example.com&quot;
    port: 1024
  }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;case class ServerConfig(host: String, port: Int)

import net.ceedubs.ficus.Ficus._
import net.ceedubs.ficus.readers.ArbitraryTypeReader._

ConfigFactory.load().as[ServerConfig](&quot;server&quot;)
//emits ServerConfig(&quot;example.com&quot;, 1024)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;ad_rem&quot;&gt;Ad Rem&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;first_approach&quot;&gt;First Approach&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;So, keeping with the &lt;a href=&quot;https://en.wikipedia.org/wiki/Ficus_benjamina&quot;&gt;theme&lt;/a&gt;, let&amp;#8217;s wire up a life support system for
plants. Say we have two HTTP services - one for moisture control, and the other handling miscellaneous sensors:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;services.scala&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;class HydroService(val config: ServerConfig) {
    //logic goes here
}

class SensorService(val config: ServerConfig) {
   //logic goes here
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;where, again, the config class is:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;case class ServerConfig(host: String, port: Int)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;So, our config file could look like this:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-hocon&quot; data-lang=&quot;hocon&quot;&gt;app {
  hydro {
    host: &quot;hydro.example.com&quot;
    port: 8215
  }

  sensors {
    host: &quot;sensors.example.com&quot;
    port: 1777
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;To make things sweet and simple, let&amp;#8217;s encapsulate the the entire (relevant) config into a container class:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;AppConfig.scala&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;class AppConfig(val hydro: ServerConfig, val sensors: ServerConfig)

case class ServerConfig(host: String, port: Int)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We&amp;#8217;ll now take the first stab at the application runner:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;Run.scala&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;import com.softwaremill.macwire._
import com.typesafe.config.ConfigFactory

object Run extends App {

  lazy val rawConfig = ConfigFactory.load()

  import net.ceedubs.ficus.Ficus._
  import net.ceedubs.ficus.readers.ArbitraryTypeReader._

  lazy val config: AppConfig = rawConfig.as[AppConfig](&quot;app&quot;) &lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;

  import config._ &lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;

  lazy val hydroService = wire[HydroService]
  lazy val sensorService = wire[SensorService]

  println(s&quot;HydroService configured with ${hydroService.config}&quot;) &lt;i class=&quot;conum&quot; data-value=&quot;3&quot;&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
  println(s&quot;SensorService configured with ${sensorService.config}&quot;)

}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;colist arabic&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;1&quot;&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;It is a good idea for your app&amp;#8217;s config to be contained in a dedicated subpath, mostly for reasons
of avoiding namespace clashes, but also due to &lt;a href=&quot;https://github.com/ceedubs/ficus/issues/10&quot;&gt;technical difficulties of loading the &quot;root path&quot;&lt;/a&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;2&quot;&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Bringing ServerConfig instances into scope.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class=&quot;conum&quot; data-value=&quot;3&quot;&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Debug statements.&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Obviously, we&amp;#8217;ll end up with:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-sbt&quot; data-lang=&quot;sbt&quot;&gt;&amp;gt; runMain Run
[info] Updating {file:}root-201604ficusplay...
[info] Resolving jline#jline;2.12.1 ...
[info] Done updating.
[info] Compiling 3 Scala sources to target/scala-2.11/classes...
[error] src/main/scala/Run.scala:24: Found multiple values of type [ServerConfig]: [List(sensors, hydro)]
[error]   lazy val hydroService = wire[HydroService]
[error]                               ^
[error] src/main/scala/Run.scala:25: Found multiple values of type [ServerConfig]: [List(sensors, hydro)]
[error]   lazy val sensorService = wire[SensorService]
[error]                                ^
[error] two errors found&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;because Macwire cannot distinguish between the two &lt;code&gt;ServerConfig&lt;/code&gt; instances pulled into scope with &lt;code&gt;import config._&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;So, what can we do?&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;tagging_to_the_rescue&quot;&gt;Tagging to the rescue&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Well, it turns out Macwire possesses the notion of &lt;a href=&quot;https://github.com/adamw/macwire#qualifiers&quot;&gt;Qualifiers&lt;/a&gt;. In intent, they
are identical to JSR 330 qualifiers, the only difference is that they operate on types instead of annotations (since
Macwire performs its DI based on types).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;OK, so we need to qualify our two service dependencies with some sort of tagging types. We &lt;em&gt;could&lt;/em&gt; create marker traits for that.
However, we notice that we:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;already have two distinct types&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;that unambiguously convey our intent.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;I&amp;#8217;m talking, obviously, about the service types themselves!&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;OK, so let&amp;#8217;s tag those config dependencies:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;services.scala&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;import com.softwaremill.tagging._

class HydroService(val config: ServerConfig @@ HydroService)

class SensorService(val config: ServerConfig @@ SensorService)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Of course, for MacWire to know where to get these dependencies from, we have to tag the config class as well:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;AppConfig.scala&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;import com.softwaremill.tagging._

class AppConfig(val hydro: ServerConfig @@ HydroService, val sensors: ServerConfig @@ SensorService)

case class ServerConfig(host: String, port: Int)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;OK, looks like we&amp;#8217;re all set. Let&amp;#8217;s run our app now, aaand:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-sbt&quot; data-lang=&quot;sbt&quot;&gt;[info] Compiling 1 Scala source to target/scala-2.11/classes...
[error] src/main/scala/Run.scala:20: Cannot generate a config value reader for type
com.softwaremill.tagging.@@[ServerConfig,HydroService], because value readers cannot be auto-generated for types with type parameters.
Consider defining your own ValueReader[com.softwaremill.tagging.@@[ServerConfig,HydroService]]
[error]   lazy val config: AppConfig = rawConfig.as[AppConfig](&quot;app&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Still no go.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;rescuing_the_tagging&quot;&gt;Rescuing the tagging&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The error message pretty much spells out the problem &lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_1&quot; class=&quot;footnote&quot; href=&quot;#_footnote_1&quot; title=&quot;View footnote.&quot;&gt;1&lt;/a&gt;]&lt;/sup&gt;.
Since the config instances are now of type &lt;code&gt;@@[ServerConfig,XService]&lt;/code&gt;, Ficus is unable to find a way to construct
instances of their types.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Via the error message, we&amp;#8217;re offered a solution of implementing a &lt;code&gt;ValueReader&lt;/code&gt;, which is what Ficus depends on when transforming config files into
  instances. Fortunately, from our previous attempt, we know that Ficus already has &lt;code&gt;ValueReader&lt;/code&gt; objects in scope capable of generating case classes like &lt;code&gt;ServerConfig&lt;/code&gt;
  &lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_2&quot; class=&quot;footnote&quot; href=&quot;#_footnote_2&quot; title=&quot;View footnote.&quot;&gt;2&lt;/a&gt;]&lt;/sup&gt;.
Additionally:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ValueReader&lt;/code&gt; has a &lt;code&gt;map&lt;/code&gt; method,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;MacWire provides a &lt;code&gt;taggedWith[TTag]&lt;/code&gt; helper that converts any type &lt;code&gt;TType&lt;/code&gt; into &lt;code&gt;@@[TType, TTag]&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Let&amp;#8217;s put those pieces together and add our custom reader for tagged types:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;implicit def taggedReader[TType, TTag](implicit reader: ValueReader[TType]) = reader.map(_.taggedWith[TTag])&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;After we add the above to run, we should end up with:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-sbt&quot; data-lang=&quot;sbt&quot;&gt;&amp;gt; runMain Run
[info] Compiling 1 Scala source to target/scala-2.11/classes...
[info] Running Run
HydroService configured with ServerConfig(hydro.example.com,8215)
SensorService configured with ServerConfig(sensors.example.com,1777)
[success] Total time: 2 s, completed 2016-04-30 15:08:12&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;And we&amp;#8217;re pretty much done!&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;One final improvement that we can make is to better convey the relationship between the transformed type and &lt;code&gt;ValueReader&lt;/code&gt;. We do this
by using the following equivalent form of our reader:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;implicit def taggedReader[TType: ValueReader, TTag] = implicitly[ValueReader[TType]].map(_.taggedWith[TTag])&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In the end, our app class looks like this:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;title&quot;&gt;Run.scala&lt;/div&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;highlight&quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;import com.softwaremill.macwire._
import com.softwaremill.tagging._
import com.typesafe.config.ConfigFactory
import net.ceedubs.ficus.readers.ValueReader

object Run extends App {

  lazy val rawConfig = ConfigFactory.load()

  import net.ceedubs.ficus.Ficus._
  import net.ceedubs.ficus.readers.ArbitraryTypeReader._


  implicit def taggedReader[TType: ValueReader, TTag] = implicitly[ValueReader[TType]].map(_.taggedWith[TTag])

  lazy val config: AppConfig = rawConfig.as[AppConfig](&quot;app&quot;)

  import config._

  lazy val hydroService = wire[HydroService]
  lazy val sensorService = wire[SensorService]

  println(s&quot;HydroService configured with ${hydroService.config}&quot;)
  println(s&quot;SensorService configured with ${sensorService.config}&quot;)

}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We&amp;#8217;ve managed to get Macwire and Ficus happily working together to parse HOCON config files, in a type-safe manner.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Note that creating a master config object in larger, multi-module projects, is a Bad Idea™. However, that&amp;#8217;s not a problem, since
you can just pass on the &quot;raw&quot; config (from &lt;code&gt;typesafe-config&lt;/code&gt;) onto the relevant submodules, and have Ficus generate the
 actual config instances there.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Overall, this approach scales well (architecturally), and requires only the minimal boilerplate necessary for type safety.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;You can find the full code example &lt;a href=&quot;https://github.com/mikolak-net/blog201604ficusmacwire&quot;&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_1&quot;&gt;
&lt;a href=&quot;#_footnoteref_1&quot;&gt;1&lt;/a&gt;. If only more Scala libs took this degree of care with compile-time messages&amp;#8230;&amp;#8203;
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_2&quot;&gt;
&lt;a href=&quot;#_footnoteref_2&quot;&gt;2&lt;/a&gt;. Since the original approach went pass the &quot;Ficus stage&quot; successfully, and only emitted an error during wiring.
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Learn AND remember with Anki: Part 4: Statistics and Conclusions</title>
      <link>https://xn--mikoak-6db.net/blog/2015/srs-assisted-learning-stats-and-conclusions.html</link>
      <pubDate>Wed, 26 Aug 2015 00:00:00 +0200</pubDate>
      <guid isPermaLink="false">blog/2015/srs-assisted-learning-stats-and-conclusions.html</guid>
      	<description>
	&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;previously&quot;&gt;Previously&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We&amp;#8217;ve &lt;a href=&quot;http://mikołak.net/blog/2015/srs-assisted-learning-scheme-practice.html&quot;&gt;just went over a practical example of the scheme&lt;/a&gt;. Now, let&amp;#8217;s talk stats and conclusions.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;so_how_does_it_work_out_really&quot;&gt;So how does it work out, really?&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;While I can&amp;#8217;t offer you any peer-reviewed, &lt;a href=&quot;https://en.wikipedia.org/wiki/Blind_experiment#Double-blind_trials&quot;&gt;double-blind&lt;/a&gt; study on the
validity of the method I&amp;#8217;ve described above. &lt;em&gt;Subjectively&lt;/em&gt; however, I can tell you that I am convinced adopting the technique has had
a great effect on improving the time effectiveness of learning stuff I want to know. Without fail, for every such source, whether
a book, a course, technical documentation or otherwise, I have progressed beyond just that &quot;feel-good&quot; sensation and actually was able
 to recall the most important information many, many months after first absorbing it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;However, I do have some objective, if not statistically representative, data. Take a look at the following figures, which describe
 the total time and total number of card reviews throughout my entire usage history, with my ~850 card collection, grown over time:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock thumb&quot; style=&quot;text-align: center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//anki//anki-stats.png&quot; alt=&quot;anki stats&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Stats since day 1. Note the &quot;Average for days studied&quot; entry.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As you can see, the amount of time per day, even if artificially inflated by the aforementioned commuting, is laughably small
when compared to the benefits. The benefits themselves, in turn, are indirectly demonstrated by the portion of &quot;relearn&quot; (i.e. &quot;I forgot all
about it&quot;) card reviews in the overall scheme - Anki/SRS really is that effective in helping you remember &lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_1&quot; class=&quot;footnote&quot; href=&quot;#_footnote_1&quot; title=&quot;View footnote.&quot;&gt;1&lt;/a&gt;]&lt;/sup&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;in_closing&quot;&gt;In closing&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;It would be a disservice to Anki if I&amp;#8217;d fail to mention that it is also an excellent tool for helping in learning a language. Anki was, after all,
 &lt;a href=&quot;https://groups.google.com/d/msg/anki-users/M8mDFN7-H-I/P2bRBmm2aQEJ&quot;&gt;originally created to aid its author in studying Japanese&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In fact, a comprehensive collection of community-created decks &lt;a href=&quot;https://ankiweb.net/shared/decks/&quot;&gt;has been made available&lt;/a&gt;, spanning
 not only multiple languages but also subjects such as Biology, Geography, Physics, and others.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Finally, I hope I have shared some of the enthusiasm for Spaced-Repetition-assisted learning in the context of technical knowledge &lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_2&quot; class=&quot;footnote&quot; href=&quot;#_footnote_2&quot; title=&quot;View footnote.&quot;&gt;2&lt;/a&gt;]&lt;/sup&gt;. I encourage you to try it out for a small dataset, and see how it goes - as long as you have a minimum of self-control and curiosity, you should benefit
from adopting this technique. Maybe it &lt;a href=&quot;https://en.wikipedia.org/w/index.php?title=Roger_Craig_%28Jeopardy!_contestant%29&amp;amp;oldid=651710417#Preparation&quot;&gt;won&amp;#8217;t help &lt;strong&gt;you&lt;/strong&gt; win &quot;Jeopardy!&quot;&lt;/a&gt;,
but it will certainly provide you with a powerful tool when dealing with the ever increasing corpus of information that must be absorbed in
order to stay up to date, and to keep in touch with the &quot;core&quot; knowledge that serves as the foundation for your daily technical decisions.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_1&quot;&gt;
&lt;a href=&quot;#_footnoteref_1&quot;&gt;1&lt;/a&gt;. A recent example - after preparing a simple deck, and some minimal practice, I was able to write complex MongoDB aggregations off the top of my head, many months after completing the related online course.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_2&quot;&gt;
&lt;a href=&quot;#_footnoteref_2&quot;&gt;2&lt;/a&gt;. and haven&amp;#8217;t bored you to tears while at it.
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Learn AND remember with Anki: Part 3: From theory to practice</title>
      <link>https://xn--mikoak-6db.net/blog/2015/srs-assisted-learning-scheme-practice.html</link>
      <pubDate>Tue, 4 Aug 2015 00:00:00 +0200</pubDate>
      <guid isPermaLink="false">blog/2015/srs-assisted-learning-scheme-practice.html</guid>
      	<description>
	&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;previously&quot;&gt;Previously&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://mikołak.net/blog/2015/srs-assisted-learning-scheme-theory.html&quot;&gt;During the last episode&lt;/a&gt;, we&amp;#8217;ve discussed a learning scheme that
can help you to avoid having most acquired information from floating away.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In what follows, we&amp;#8217;ll use an example to walk through the scheme step by step.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sidebarblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;div class=&quot;title&quot;&gt;Intended audience&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;While the bulk of this series offers information that should be useful for virtually everyone, the examples below are tailored towards programmers,
specifically &lt;a href=&quot;https://en.wikipedia.org/w/index.php?title=Java_virtual_machine&amp;amp;oldid=659766895&quot;&gt;JVM&lt;/a&gt; polyglot programmers. You have been warned.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;enough_theory_time_for_practice&quot;&gt;Enough theory, time for practice&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;step_0_choose_what_to_learn&quot;&gt;Step 0: Choose what to learn&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;For this article, I&amp;#8217;ve decided to use the example of &lt;a href=&quot;http://www.scalatest.org/user_guide/using_matchers&quot;&gt;ScalaTest matchers&lt;/a&gt;.
That topic makes a good example, because:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;the subjects squarely in the category of &quot;in one ear, out the other&quot; ,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;remembering brings a tangible benefit - reports for the specialised matchers are more meaningful than those from a simple &lt;code&gt;should be equal&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Let&amp;#8217;s start, shall we?&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;step_1_highlight_key_passages_optional&quot;&gt;Step 1: Highlight Key Passages (Optional)&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The matcher documentation (again, &lt;a href=&quot;http://www.scalatest.org/user_guide/using_matchers&quot;&gt;available here&lt;/a&gt;) is actually a
good case where this step can be skipped. The docs are compact, information-rich, and the most notable facts are plainly
 visible.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;step_2_create_mind_map_like_notes&quot;&gt;Step 2: Create Mind-map-like notes&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Before we start, if you don&amp;#8217;t have a favorite mind-mapping application,
&lt;a href=&quot;http://freemind.sourceforge.net/wiki/index.php/Download&quot;&gt;download and install Freemind&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;First of all, we&amp;#8217;re not actually going to do the full docs, just enough to create a relatively good example. So, let&amp;#8217;s do
the first 3 sections, up to and including &quot;checking strings&quot;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Here&amp;#8217;s how a note-centered mind map might look like:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock thumb&quot; style=&quot;text-align: center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//anki//freemind-scalatest.png&quot; alt=&quot;freemind scalatest&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;The resultant mind map.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;You might notice the following things:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;consistently with &quot;normal&quot; mind maps, items progressively become less general and more specific the further away from the center,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;also like &quot;normal&quot; mind maps, items connected &quot;physically&quot; are also related semantically &lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_1&quot; class=&quot;footnote&quot; href=&quot;#_footnote_1&quot; title=&quot;View footnote.&quot;&gt;1&lt;/a&gt;]&lt;/sup&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;unlike&lt;/em&gt; typical mind maps, there very little formatting and decorations were applied. This is because the map is intended to be a transitory step,
and not something you will revisit often. Normally, the only formatting I do is hyperlinks and icons for denoting especially important stuff.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;only a portion of the information from the doc page is included. It&amp;#8217;s a deliberate decision - the goal is to provide a source for
quickly writing up cards, which are supposed to help you in the long-term - so we&amp;#8217;re looking for stuff that&amp;#8217;s most useful. In other words,
we need the info for your regular life/work, and &lt;strong&gt;not&lt;/strong&gt; to prepare for a fire-and-forget exam.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;step_3_extract_flashcards_from_the_mind_map_in_anki_desktop&quot;&gt;Step 3: Extract flashcards from the mind map in Anki Desktop&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;If you haven&amp;#8217;t done so already, &lt;a href=&quot;http://ankisrs.net/#download&quot;&gt;download and install Anki on your computer&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;First, click &lt;code&gt;Create Deck&lt;/code&gt; &lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_2&quot; class=&quot;footnote&quot; href=&quot;#_footnote_2&quot; title=&quot;View footnote.&quot;&gt;2&lt;/a&gt;]&lt;/sup&gt;. Since I already have a &lt;code&gt;Scala&lt;/code&gt; deck,
I&amp;#8217;m going to make a subdeck, which is done by writing &lt;code&gt;Scala::ScalaTest&lt;/code&gt;. After the deck is created, click on it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot; style=&quot;text-align: center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//anki//anki-newdeck.png&quot; alt=&quot;anki newdeck&quot; width=&quot;720&quot; height=&quot;1&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Ready to add cards.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Now, we&amp;#8217;re ready to create the cards proper. Click on &lt;code&gt;Add&lt;/code&gt; at the top &lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_3&quot; class=&quot;footnote&quot; href=&quot;#_footnote_3&quot; title=&quot;View footnote.&quot;&gt;3&lt;/a&gt;]&lt;/sup&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot; style=&quot;text-align: center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//anki//anki-newcard.png&quot; alt=&quot;anki newcard&quot; width=&quot;500&quot; height=&quot;1&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;New card to be filled out.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;You&amp;#8217;ll be presented with a view similar to the above (the additional UI elements mostly come from the &lt;a href=&quot;https://ankiweb.net/shared/info/491274358&quot;&gt;source code plugin&lt;/a&gt;).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We&amp;#8217;re going to out of order and start with the second point - asserting size and/or length - since it&amp;#8217;s more straightforward. Let&amp;#8217;s start with the length:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Front: I recommend this to be titled in the way that is the most similar to how you&amp;#8217;d try to recall the relevant information. In this case, I&amp;#8217;d
write &quot;ScalaTest - asserting length&quot;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Back: this should contain the relevant information in the most concise way. In this case, all we need is the text of the assert, e.g. &lt;code&gt;have length X&lt;/code&gt;
&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_4&quot; class=&quot;footnote&quot; href=&quot;#_footnote_4&quot; title=&quot;View footnote.&quot;&gt;4&lt;/a&gt;]&lt;/sup&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot; style=&quot;text-align: center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//anki//anki-newcard-filled.png&quot; alt=&quot;anki newcard filled&quot; width=&quot;500&quot; height=&quot;1&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Card filled out.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Now, do the same for asserting size. Here you might think whether it wouldn&amp;#8217;t be more efficient to store both pieces of information in a single card. In
practice, I find that it&amp;#8217;s much more worthwhile to split information into as many cards as possible, as simple cards are much easier to learn and hence
produce a better retention effect.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Going up next, we have the string comparisons. Here, I&amp;#8217;d suggest to create 8 cards:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;3 per each &quot;simple&quot; matcher&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;4 per each &quot;regex&quot; matcher&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;1 for the &lt;code&gt;withGroups&lt;/code&gt; matcher qualifier.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot; style=&quot;text-align: center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//anki//anki-card-simple-filled.png&quot; alt=&quot;anki card simple filled&quot; width=&quot;500&quot; height=&quot;1&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Simple comparison, note slogan-like front naming.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;After that, we&amp;#8217;ve got comparisons. Here, I&amp;#8217;m going to create just two cards, one per every strict/equal-or pair, since the cases appear to be both
semantically and &quot;graphically&quot; coupled.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot; style=&quot;text-align: center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//anki//anki-card-standard.png&quot; alt=&quot;anki card standard&quot; width=&quot;500&quot; height=&quot;1&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Two pieces of info, but still pretty straightforward.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Note that here I&amp;#8217;m omitting the &lt;code&gt;Ordering[T]&lt;/code&gt; information, as I&amp;#8217;m making the assumption that I can rely on the compiler for recalling that tidbit.
&lt;em&gt;However&lt;/em&gt;, if you e.g. use lots of custom classes with dedicated &lt;code&gt;Ordering&lt;/code&gt; implementations, creating an additional card might be worth your while.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Finally, let&amp;#8217;s go back to the general equality case. The &lt;code&gt;be&lt;/code&gt; card is pretty straightforward:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot; style=&quot;text-align: center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//anki//anki-card-complex-entry.png&quot; alt=&quot;anki card complex entry&quot; width=&quot;500&quot; height=&quot;1&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Simple equality.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;However, for the &lt;code&gt;equal&lt;/code&gt; we have to cram some info on the back:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot; style=&quot;text-align: center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//anki//anki-card-complex.png&quot; alt=&quot;anki card complex&quot; width=&quot;500&quot; height=&quot;1&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;The most complex card type you should be making.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This is unfortunate, however sometimes you have pieces of information that are inseparable.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;And there you have it! If you want to study using the cards created for this section, download &lt;a href=&quot;../../illustrations//anki/Scala__ScalaTest.apkg&quot;&gt;the pack from here&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;step_4_learn_from_flashcards_in_the_anki_mobile_client_s&quot;&gt;Step 4: Learn from flashcards in the Anki Mobile Client(s)&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Now that we&amp;#8217;ve created the cards, we can move to actually using them. For that, you&amp;#8217;ll need to:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;Get the mobile client, in either the &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.ichi2.anki&quot;&gt;Android&lt;/a&gt; or
&lt;a href=&quot;https://itunes.apple.com/us/app/ankimobile-flashcards/id373493387?mt=8&amp;amp;ign-mpt=uo%3D4&quot;&gt;iOS&lt;/a&gt; versions&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set up &lt;a href=&quot;http://ankisrs.net/docs/manual.html#ankiweb-and-synchronization&quot;&gt;AnkiWeb synchronization&lt;/a&gt; between your desktop client
and your mobile device.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;admonitionblock important&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td class=&quot;icon&quot;&gt;
&lt;i class=&quot;fa icon-important&quot; title=&quot;Important&quot;&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class=&quot;content&quot;&gt;
Please be aware of the &lt;a href=&quot;https://ankiweb.net/account/terms&quot;&gt;content policies&lt;/a&gt; before you sync data through AnkiWeb.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Now that you&amp;#8217;ve set it up, and open the synced deck, you should see something like the following:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock thumb&quot; style=&quot;text-align: left&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//anki//anki-mobile-starting.png&quot; alt=&quot;anki mobile starting&quot; width=&quot;230&quot; height=&quot;1&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Before&amp;#8230;&amp;#8203;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock thumb&quot; style=&quot;text-align: right&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//anki//anki-mobile-starting-answer.png&quot; alt=&quot;anki mobile starting answer&quot; width=&quot;230&quot; height=&quot;1&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;&amp;#8230;&amp;#8203;and after the first answer.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;You&amp;#8217;re viewing a new card in the learning stage, which will require at least one repetition on the same day. Always choose the
option that authentically reflects your recall ability of that card. Eventually, you should arrive at something similar to
the following screen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock thumb&quot; style=&quot;text-align: center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//anki//anki-mobile-final-review.png&quot; alt=&quot;anki mobile final review&quot; width=&quot;230&quot; height=&quot;1&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Last answer for now. Note the different time periods.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Now the choices extend beyond the same day. If your recall is increased effectively, the interval selection will lengthen at
a geometric rate. So when in doubt, answer pessimistically - it won&amp;#8217;t &quot;cost&quot; you much time.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sidebarblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;div class=&quot;title&quot;&gt;Use the Widget&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;If you&amp;#8217;re using Android, the widget is a nice feature. It shows you the amount of pending cards for the day, as well
as the projected time required for going over them.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;step_5_review_heal_and_grow_your_flashcard_deck&quot;&gt;Step 5: Review, Heal, and Grow your flashcard &quot;Deck&quot;&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This is probably the most personalized of the steps. However, a couple common issues might manifest, most notably:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;errors_on_the_cards&quot;&gt;Errors on the cards&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Factual, typographical or otherwise: minor ones can be remedied immediately through the mobile client, but
for more complex stuff, your best bet is to mark the card by favoriting it, and potentially &lt;a href=&quot;http://ankisrs.net/docs/manual.html#editing-and-more&quot;&gt;burying the card&lt;/a&gt;, &quot;postponing&quot; it
to the following day, if the problem is severe enough so that you can&amp;#8217;t meaningfully review the card &lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_5&quot; class=&quot;footnote&quot; href=&quot;#_footnote_5&quot; title=&quot;View footnote.&quot;&gt;5&lt;/a&gt;]&lt;/sup&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock thumb&quot; style=&quot;float: right&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//anki//anki-bury-favorite.png&quot; alt=&quot;anki bury favorite&quot; width=&quot;230&quot; height=&quot;1&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;&lt;code&gt;Hide/Delete&lt;/code&gt; will show &lt;code&gt;Bury&lt;/code&gt;.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;lack_of_perceived_benefit_from_using_the_cards_for_a_em_particular_em_topic&quot;&gt;Lack of perceived benefit from using the cards for a &lt;em&gt;particular&lt;/em&gt; topic&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This mostly stems from not enough understanding
of the subject matter. With this method, always try to make a conscious effort to actually &lt;em&gt;comprehend&lt;/em&gt; and &lt;em&gt;integrate&lt;/em&gt; the source material.
This even applies to our relatively simple example - the retention effect for the ScalaTest will be much greater if you practice
writing the matcher expressions during the the initial days.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;general_frustration&quot;&gt;General frustration&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In other words, the bog standard reaction to a new habit that one is trying to form. After the initial enthusiasm dwindles,
the act of repeating the cards might appear like a chore. In this case, I advise you to&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;simply stick it out if you&amp;#8217;re only a couple of weeks in,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;be honest - if you forgot the card&amp;#8217;s content, choose &quot;Again&quot;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;otherwise try to remember situations where the use of this technique has improve recall, and&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;take note of how much time have you&amp;#8217;ve already spent by using this method &lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_6&quot; class=&quot;footnote&quot; href=&quot;#_footnote_6&quot; title=&quot;View footnote.&quot;&gt;6&lt;/a&gt;]&lt;/sup&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;recalling_knowledge_you_no_longer_need&quot;&gt;Recalling knowledge you no longer need&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;It happens, whether when studying for actual exams, investing in learning a technology that
didn&amp;#8217;t pan out, or for other reasons. In this case, you just need to &lt;a href=&quot;http://ankisrs.net/docs/manual.html#editing-and-more&quot;&gt;suspend&lt;/a&gt;
the relevant cards, causing them to no longer appear until they&amp;#8217;re manually resumed.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;amassing_a_huge_backlog&quot;&gt;Amassing a huge backlog&lt;/h4&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;To prevent that, start small (&amp;lt;100 cards total) until you get the gist of it, and try to set aside
some regularly scheduled period in your daily routine for the card review - for example, during your daily commute &lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_7&quot; class=&quot;footnote&quot; href=&quot;#_footnote_7&quot; title=&quot;View footnote.&quot;&gt;7&lt;/a&gt;]&lt;/sup&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To resolve it, just chip away at the mass of cards for several days. Due to how the SRS algorithm works, you are bound to eventually reduce the
backlog to a manageable size.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;coming_up&quot;&gt;Coming Up&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In &lt;a href=&quot;http://mikołak.net/blog/2015/srs-assisted-learning-stats-and-conclusions.html&quot;&gt;the final part of the series&lt;/a&gt;, we&amp;#8217;ll look at some actual statistics related to using Anki, and touch on some miscellaneous
closing points.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_1&quot;&gt;
&lt;a href=&quot;#_footnoteref_1&quot;&gt;1&lt;/a&gt;. There are no additional connections in this particular example, but, in general, it&amp;#8217;s OK to make them.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_2&quot;&gt;
&lt;a href=&quot;#_footnoteref_2&quot;&gt;2&lt;/a&gt;. A deck is simply a collection of Anki cards.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_3&quot;&gt;
&lt;a href=&quot;#_footnoteref_3&quot;&gt;3&lt;/a&gt;. Yes, the Anki UI is sometimes all over the place.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_4&quot;&gt;
&lt;a href=&quot;#_footnoteref_4&quot;&gt;4&lt;/a&gt;. Pardon the faint red markings on the illustration - there seems to be a bug in the Anki Linux client that makes disabling the spellcheck impossible, hence a quick photoedit job.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_5&quot;&gt;
&lt;a href=&quot;#_footnoteref_5&quot;&gt;5&lt;/a&gt;. Often happens with formulas encoded in LaTeX, as the mobile client  is notoriously fickle when displaying them.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_6&quot;&gt;
&lt;a href=&quot;#_footnoteref_6&quot;&gt;6&lt;/a&gt;. Yes, I&amp;#8217;m basically suggesting an abuse of humanity&amp;#8217;s susceptibility to the &lt;a href=&quot;https://en.wikipedia.org/w/index.php?title=Gambler%27s_fallacy&amp;amp;oldid=663853840&quot;&gt;Gambler&amp;#8217;s Fallacy&lt;/a&gt;.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_7&quot;&gt;
&lt;a href=&quot;#_footnoteref_7&quot;&gt;7&lt;/a&gt;. Which was how I started.
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Learn AND remember with Anki: Part 2: Putting it all together</title>
      <link>https://xn--mikoak-6db.net/blog/2015/srs-assisted-learning-scheme-theory.html</link>
      <pubDate>Tue, 23 Jun 2015 00:00:00 +0200</pubDate>
      <guid isPermaLink="false">blog/2015/srs-assisted-learning-scheme-theory.html</guid>
      	<description>
	&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;previously&quot;&gt;Previously&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In the &lt;a href=&quot;http://mikołak.net/blog/2015/srs-assisted-learning-intro.html&quot;&gt;starting article&lt;/a&gt;, I&amp;#8217;ve provided a quick overview of what Spaced Repetition means,
and talked about the tools that can facilitate it. Now, let&amp;#8217;s see how we can put these tools to good use.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;the_scheme&quot;&gt;The Scheme&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Based on using Anki for knowledge retention, I&amp;#8217;ve developed a learning scheme to keep myself from forgetting the stuff
I&amp;#8217;d rather remember. Given a new source material (e.g. a book, an online course), here&amp;#8217;s how it looks like.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;step_0_choose_what_to_learn&quot;&gt;Step 0: Choose what to learn&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Probably the most important step - you need to &lt;strong&gt;consciously&lt;/strong&gt;, and &lt;strong&gt;responsibly&lt;/strong&gt;, ask yourself the question: &quot;what knowledge do I need to retain
effectively&quot;? This is something that you optimally should work out for yourself.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;However, I can provide three pointers:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The best fit should align with one of the two categories I&amp;#8217;ve described in the &lt;a href=&quot;#the_perils_of_ad_hoc_learning&quot;&gt;introductory sections&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You already need to &lt;strong&gt;understand&lt;/strong&gt; whatever you&amp;#8217;re trying to recall - the scheme I&amp;#8217;m describing want help you with that.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Don&amp;#8217;t&lt;/strong&gt; start with adding &quot;feel-good stuff&quot; - knowledge that you always wanted to recall off the top of your head in order to impress yourself.
You&amp;#8217;ll most likely, eventually get frustrated and see the exercise as a waste of time. Do begin with applying this learning method to things
 you actually &lt;strong&gt;need&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;step_1_highlight_key_passages_optional&quot;&gt;Step 1: Highlight Key Passages (Optional)&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This is of course only relevant for textual sources. I still recommend it whenever you&amp;#8217;re learning from those, because
it helps you stay in the flow.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;step_2_create_mind_map_like_notes&quot;&gt;Step 2: Create Mind-map-like notes&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Regularly, for each piece of content (video for online courses, chapter for a book, etc.), add notes to a mind map describing
the thing you&amp;#8217;re currently learning about &lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_1&quot; class=&quot;footnote&quot; href=&quot;#_footnote_1&quot; title=&quot;View footnote.&quot;&gt;1&lt;/a&gt;]&lt;/sup&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This may seem like a superfluous step, but it helps in three things:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;it actually helps with knowledge retention by preliminarily organizing it,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;it makes the next step go faster, since you don&amp;#8217;t have to re-read/-watch the entire content,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;most importantly, it helps to verify whether you&amp;#8217;ve understood what you&amp;#8217;re learning about. You will very likely to see
that you&amp;#8217;re e.g. writing nonsense at this point, and it&amp;#8217;s less frustrating to identify it and fix it than in the next
step.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;admonitionblock note&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td class=&quot;icon&quot;&gt;
&lt;i class=&quot;fa icon-note&quot; title=&quot;Note&quot;&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class=&quot;content&quot;&gt;
I say &quot;mind-map-like&quot; because someone might argue that the notes might not necessarily look like a &quot;proper&quot; mind map, i.e.
there are no colors, graphics, etc. Nevertheless, I think the form of a tree, which mindmaps take, is the most conducive
 to general, rapid note-taking.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;For the actual mind mapping I prefer to use &lt;a href=&quot;http://freemind.sourceforge.net/wiki/index.php/Main_Page&quot;&gt;Freemind&lt;/a&gt;. It&amp;#8217;s open source,
free, multiplatform, and offers a set of convenient keyboard shortcuts for quick note-taking.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock thumb&quot; style=&quot;text-align: center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//anki//freemind-example.png&quot; alt=&quot;freemind example&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;A mind-map example - an (outdated) mind map of Akka documentation.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;step_3_extract_flashcards_from_the_mind_map_in_anki_desktop&quot;&gt;Step 3: Extract flashcards from the mind map in Anki Desktop&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Now that you have rapid-access notes, it&amp;#8217;s time to go through them and create the flashcards. We&amp;#8217;ll mostly be dealing with
&quot;classic cards&quot;, i.e. with two faces - an entry, and whatever you should recall according regarding the entry. However, there
are also other types: reversed (where the &quot;entry&quot;-&quot;recall&quot; relation is bilateral, effectively doubling the given card), and cloze
completion, where you &quot;fill&quot; in text (useful for e.g. learning phrases in foreign languages).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Also, you can insert almost everything into a card, including:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;plain and formatted texts,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;pictures,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;sounds,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;LaTeX, including complex formulas in math mode,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;source code, &lt;a href=&quot;https://ankiweb.net/shared/info/491274358&quot;&gt;with the correct plugin&lt;/a&gt; &lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_2&quot; class=&quot;footnote&quot; href=&quot;#_footnote_2&quot; title=&quot;View footnote.&quot;&gt;2&lt;/a&gt;]&lt;/sup&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;See the figure &lt;a href=&quot;#an_sr_based_approach&quot;&gt;in the introductory section&lt;/a&gt; for some examples.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;step_4_learn_from_flashcards_in_the_anki_mobile_client_s&quot;&gt;Step 4: Learn from flashcards in the Anki Mobile Client(s)&lt;/h3&gt;
&lt;div class=&quot;imageblock thumb&quot; style=&quot;float: right&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//anki//anki-learning-example.png&quot; alt=&quot;anki learning example&quot; width=&quot;230&quot; height=&quot;1&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Reviewing a card in Anki.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Whereas the Desktop client is more conducive to creating (and reviewing) cards, I find it much better to actually learn
by using the Anki software on mobile devices (iOS/Android &lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_3&quot; class=&quot;footnote&quot; href=&quot;#_footnote_3&quot; title=&quot;View footnote.&quot;&gt;3&lt;/a&gt;]&lt;/sup&gt;).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Principally, this is because, with the mobile client, you have less distractions. And trust me, during the beginnings,
once the initial enthusiasm wears out but &lt;em&gt;before&lt;/em&gt; you see tangible benefits, you will need all the focus you can get.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Another, equally important thing is that you will have access to the cards wherever you go. This makes it an easier to
form the habit of reviewing the cards daily, especially in the case of the Android client, where you can also add a widget
to your home screen.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;step_5_review_heal_and_grow_your_flashcard_deck&quot;&gt;Step 5: Review, Heal, and Grow your flashcard &quot;Deck&quot;&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Your cards, once created, should &lt;em&gt;not&lt;/em&gt; be treated as if set in stone. Splitting a card, improving it, suspending it, or removing
it altogether should all be options you need to consider.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;coming_up&quot;&gt;Coming Up&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In &lt;a href=&quot;http://mikołak.net/blog/2015/srs-assisted-learning-scheme-practice.html&quot;&gt;the next installment&lt;/a&gt;, we&amp;#8217;ll go from theory to practice and see how it pans out.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_1&quot;&gt;
&lt;a href=&quot;#_footnoteref_1&quot;&gt;1&lt;/a&gt;. I usually keep a map e.g. per book.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_2&quot;&gt;
&lt;a href=&quot;#_footnoteref_2&quot;&gt;2&lt;/a&gt;. Did I mention Anki has &lt;a href=&quot;https://ankiweb.net/shared/addons/&quot;&gt;a large plugin directory&lt;/a&gt;?
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_3&quot;&gt;
&lt;a href=&quot;#_footnoteref_3&quot;&gt;3&lt;/a&gt;. While the Android version is completely Open Source, the iOS version is paid-for, and, in fact, the latter is currently the main source of monetary support from the project.
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Learn AND remember with Anki: Part 1: Introduction</title>
      <link>https://xn--mikoak-6db.net/blog/2015/srs-assisted-learning-intro.html</link>
      <pubDate>Tue, 16 Jun 2015 00:00:00 +0200</pubDate>
      <guid isPermaLink="false">blog/2015/srs-assisted-learning-intro.html</guid>
      	<description>
	&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;intro&quot;&gt;Intro&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Have you ever:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;regretted that you only remember some bare trivia from a course or a book that you studied several months ago? Or
maybe even stuff from university/college that you wish you&amp;#8217;d recall now?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;constantly annoy yourself with looking up some implementation detail, API definition or another fact,
that you need every couple of weeks, but juuuust manage to slip your mind the next time you require it?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;feel like learning new stuff (perhaps to progress beyond your current job) is a &lt;a href=&quot;https://en.wikipedia.org/wiki/Sisyphus&quot;&gt;Sisyphean&lt;/a&gt;
task that doesn&amp;#8217;t net you anything?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;If so, than this series of articles is for you.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;the_perils_of_ad_hoc_learning&quot;&gt;The Perils of Ad-Hoc Learning&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The nice thing about living now is having slightly more free time for your basic non-survival needs. The absolutely
scary thing, in turn, is that the amount of information you have access to, and can gain, is simply overwhelming. Understandably
this becomes even worse for IT professionals, what with a new library/framework coming out every week &lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_1&quot; class=&quot;footnote&quot; href=&quot;#_footnote_1&quot; title=&quot;View footnote.&quot;&gt;1&lt;/a&gt;]&lt;/sup&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Of course, a lot of this information is fire-and-forget. Another portion you use day to day and, by this virtue, remember
without any problems.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;What&amp;#8217;s left can be divided into two broad categories.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;One is knowledge that you use in irregular, but relatively frequent intervals. An example would be an API quirk that
you keep re-reading about every couple of weeks. Irritating, isn&amp;#8217;t it? Also wasteful.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The other contains &quot;core&quot; knowledge - stuff that you&amp;#8217;re not &lt;em&gt;necessarily&lt;/em&gt; using directly, but nevertheless benefit from
recalling it readily. Forgetting this kind of information is much more insidious - you just end up doing stuff less effectively; or, perhaps
one day you suddenly realize that you completely forgot all the things you wanted to learn from the course you did half a
  year ago.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sidebarblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;div class=&quot;title&quot;&gt;But, but, the Internet!&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;A commonly raised counterargument against investing time in recall methods boils down to:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Why bother? Everything is on the Internet anyway, if I don&amp;#8217;t remember something, I&amp;#8217;ll just look it up!&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;And sure, you can do that. But how much does it cost you?&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Here&amp;#8217;s an example: remind yourself of the last small fact or info tidbit that you required and you needed to search for. Now,
take your favorite stopwatch app &lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_2&quot; class=&quot;footnote&quot; href=&quot;#_footnote_2&quot; title=&quot;View footnote.&quot;&gt;2&lt;/a&gt;]&lt;/sup&gt;,
and try out finding the answer on the Internet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;I&amp;#8217;d be willing to bet that for most of you, the total time between switching away from this article, and returning to it after searching will
be slightly north of 10 seconds.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;That&amp;#8217;s not a lot, right? But those search bits add up during the day, &lt;strong&gt;and&lt;/strong&gt; you need to factor in the
&lt;a href=&quot;https://en.wikipedia.org/wiki/Task_switching&quot;&gt;context switch&lt;/a&gt; costs
&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_3&quot; class=&quot;footnote&quot; href=&quot;#_footnote_3&quot; title=&quot;View footnote.&quot;&gt;3&lt;/a&gt;]&lt;/sup&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Now try to recall the answer again, from memory. You, quite likely, still can, and it probably took you less than a second to
remember. That is an order of magnitude of a difference. Making a somewhat strained analogy, it&amp;#8217;s quite similar to the difference between
reading from disk and reading from RAM.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Of course, you&amp;#8217;re still spending time by putting additional effort into knowledge retention - but that&amp;#8217;s &lt;em&gt;dedicated&lt;/em&gt; time,
as opposed to hacking up your work routine into disjointed bits.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;spaced_repetition_a_solution&quot;&gt;Spaced Repetition - a solution&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Obviously no one is going to sit around every day working on remember their cumulatively growing knowledge by rote.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;But there are shortcuts to do something very similar much more efficiently.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The concept of &lt;a href=&quot;https://en.wikipedia.org/w/index.php?title=Spaced_repetition&amp;amp;oldid=656222625&quot;&gt;Spaced Repetition&lt;/a&gt; offers one such
shortcut. There are many implementations exploiting the idea, but they all boil down to taking advantage of
  &lt;a href=&quot;https://en.wikipedia.org/w/index.php?title=Spacing_effect&amp;amp;oldid=635962055&quot;&gt;particularities of the human brain&lt;/a&gt; in order to
  achieve effective recall of various facts and concepts, with relatively minimal effort.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;an_sr_based_approach&quot;&gt;An SR-based approach&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;One such implementation is &lt;a href=&quot;http://ankisrs.net/&quot;&gt;Anki&lt;/a&gt;. It&amp;#8217;s &lt;a href=&quot;https://en.wikipedia.org/w/index.php?title=Flashcard&amp;amp;oldid=653079387&quot;&gt;flashcard&lt;/a&gt;-based
spaced repetition software. Its &lt;a href=&quot;http://ankisrs.net/docs/manual.html&quot;&gt;documentation can be found here&lt;/a&gt;, and it&amp;#8217;s available for download on:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://ankisrs.net/#windows&quot;&gt;Windows&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://ankisrs.net/#mac&quot;&gt;Mac OS X&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://ankisrs.net/#linux&quot;&gt;Linux&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://ankisrs.net/#ios&quot;&gt;Android and iOS&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The tl;dr version of the process of working with flashcards and SRS looks like this:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;you create a flashcard. In the simplest version it&amp;#8217;s a note with two faces: the prompt for what you want to learn, and
the thing you want to learn. Here&amp;#8217;s a couple of examples.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock&quot; style=&quot;text-align: center&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;../../illustrations//anki//anki-cards-overview.png&quot; alt=&quot;anki cards overview&quot; width=&quot;1080&quot; height=&quot;343&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;A selection of Anki cards: normal text, source code, image, LaTeX formulas.&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;after several initial repetitions, Anki prompts you with the cards, in increasing time intervals: initially a couple of days,
then a couple of weeks, months, and so on. At each repetition, when viewing the answer, you are prompted to choose one of the following options:&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&quot;Again&quot; which means you forgot about the entry, and need to reset the learning process,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&quot;Good&quot; meaning you have good recollection, i.e. the vanilla option,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&quot;Hard&quot; implying thatas you&amp;#8217;ve &lt;em&gt;sorta&lt;/em&gt; learned, but aren&amp;#8217;t quite confident, leading to a shorter time to repetition,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&quot;Easy&quot; that tells the system to extend the time interval to a greater extent than with &quot;Good&quot;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As you have probably figured it out, the biggest benefit from this system is that the time intervals are managed automatically, and you get
automatic reminders to repeat your cards (which is especially useful if you install the mobile client).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sidebarblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;div class=&quot;title&quot;&gt;Local Flavor&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Anki&amp;#8217;s repetition selection algorithm is a derivative of &lt;a href=&quot;https://en.wikipedia.org/w/index.php?title=SuperMemo&amp;amp;oldid=618419134&quot;&gt;SM2&lt;/a&gt;,
which was invented in Poland.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;coming_up&quot;&gt;Coming Up&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In &lt;a href=&quot;http://mikołak.net/blog/2015/srs-assisted-learning-scheme-theory.html&quot;&gt;the second episode of this series&lt;/a&gt;, we will talk about a learning scheme developed empirically by Yours Truly, that takes
advantage of Anki when acquiring technical (and other) knowledge.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_1&quot;&gt;
&lt;a href=&quot;#_footnoteref_1&quot;&gt;1&lt;/a&gt;. and a Javascript implementation following up in 5 days.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_2&quot;&gt;
&lt;a href=&quot;#_footnoteref_2&quot;&gt;2&lt;/a&gt;. I expect very few people still have actual stopwatches, digital or analog.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_3&quot;&gt;
&lt;a href=&quot;#_footnoteref_3&quot;&gt;3&lt;/a&gt;. Also, let&amp;#8217;s not kid ourselves, you probably took this as a challenge and done the search than you would normally do under  this kind of situation.
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Musings on Ansible</title>
      <link>https://xn--mikoak-6db.net/blog/2015/ansible-musings.html</link>
      <pubDate>Thu, 9 Apr 2015 00:00:00 +0200</pubDate>
      <guid isPermaLink="false">blog/2015/ansible-musings.html</guid>
      	<description>
	&lt;div id=&quot;preamble&quot;&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This is a set of loosely-related remarks and observations, accrued due to recent use of Ansible in several work and
personal projects (and on the latter note, &lt;a href=&quot;https://github.com/mikolak-net/ansible-raspi-config&quot;&gt;I made a thing&lt;/a&gt;).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The entry is mostly intended as a writing exercise, so if you&amp;#8217;re evaluating Ansible at the moment, please keep this
in mind.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;a_word_of_introduction&quot;&gt;A word of Introduction&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;http://www.ansible.com/home&quot;&gt;Ansible&lt;/a&gt;, like
&lt;a href=&quot;https://www.chef.io/&quot;&gt;Chef&lt;/a&gt;,
&lt;a href=&quot;https://puppetlabs.com/&quot;&gt;Puppet&lt;/a&gt; and
&lt;a href=&quot;http://saltstack.com/&quot;&gt;Salt&lt;/a&gt;, is &lt;a href=&quot;https://en.wikipedia.org/wiki/Configuration_management#Operating_System_configuration_management&quot;&gt;configuration management&lt;/a&gt;
software. Despite my best intentions to reach the widest audience possible, providing a comprehensive overview of the
field would require creating a whole series of blog posts.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Therefore, wanting to convey the following statements in reasonable time, I&amp;#8217;m forced to limit the
target readership to those that posses basic knowledge of the subject. In other words, if you are unfamiliar with the subject,
please do take your time to browse the included links. Sorry!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;ansible_the_good_the_bad_the_ugly&quot;&gt;Ansible - the good, the bad, the ugly&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;context&quot;&gt;Context&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;All those remarks are made by someone who is strictly more a dev than an op, so the things I&amp;#8217;m writing about here may
or may not apply to your situation as much as they did to mine.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Also, the entry was writen when going through the &lt;a href=&quot;https://en.wikipedia.org/wiki/Hype_cycle#/media/File:Gartner_Hype_Cycle.svg&quot;&gt;Through of Disillusionment&lt;/a&gt;,
halfway to the Slope Enlightenment, and therefore may sound more negative than intended.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;good_workerless_setup&quot;&gt;Good: Workerless setup&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The absolutely wonderful thing about Ansible is its agentless architecture - you don&amp;#8217;t need to setup anything on the
services machines, other than a valid SSH connection to a user (usually with possible &lt;code&gt;sudo&lt;/code&gt; access). No special nodes
requiring additional setup (like in Chef), nothing of the sort.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This in fact makes it very convenient to bootstrap setups for CI and the like, or even a quasi Inversion-of-Control setup,
using the &lt;a href=&quot;http://docs.ansible.com/playbooks_intro.html#ansible-pull&quot;&gt;&lt;code&gt;ansible-pull&lt;/code&gt; utility&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Such a feature may seem like a small thing, but it reduces the error rate during the &quot;metagame&quot; of setting
up your servers.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;good_easy_to_understand_syntax&quot;&gt;Good: Easy to understand syntax&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ansible uses YAML with embedded &lt;a href=&quot;http://jinja.pocoo.org/docs/&quot;&gt;Jinja2&lt;/a&gt; for its configuration definitions, and
most of the basic stuff can be expressed that way. Getting the majority of desirable output defined is pretty straightforward,
once you learn the basics.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;bad_that_s_sometimes_not_as_intuitive_as_it_should_be&quot;&gt;Bad: &amp;#8230;&amp;#8203;that&amp;#8217;s sometimes not as intuitive as it should be&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;A big stumbling block I&amp;#8217;ve encountered is correctly specifying the conditions in &lt;code&gt;when&lt;/code&gt; blocks (saying when to execute
a task) and similar ones. Truth be told, even after viewing the parsing source code for the &quot;playbooks&quot;, I&amp;#8217;m
 still not entirely confident on what is and isn&amp;#8217;t allowed.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;I think this is due to the fact that the Jinja2-based syntax sits pretty squarely in the
 &lt;a href=&quot;https://en.wikipedia.org/wiki/Uncanny_valley&quot;&gt;Uncanny Valley&lt;/a&gt; for someone with an off-and-on Python background. In
 effect, you end up writing those conditions like in Python, which works&amp;#8230;&amp;#8203; about 90% of the time. The remaining 10%
 will piss you off to no end.&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_1&quot; class=&quot;footnote&quot; href=&quot;#_footnote_1&quot; title=&quot;View footnote.&quot;&gt;1&lt;/a&gt;]&lt;/sup&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;good_has_a_strong_focus_on_idempotency&quot;&gt;Good: Has a strong focus on idempotency&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Pretty much a given for all modern configuration management software, but nevertheless I could excitedly rave and
rant about that at you the entire day - it&amp;#8217;s &lt;strong&gt;awesome&lt;/strong&gt; that Ansible specifically focuses on what the &lt;strong&gt;state should be&lt;/strong&gt;,
rather than what tasks to do.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;good_trivial_to_customize&quot;&gt;Good: Trivial to customize&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;To recap, here&amp;#8217;s how config management works in Ansible:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;the unit of work is a &lt;em&gt;task&lt;/em&gt;, meant to encapsulate a single &quot;end-state&quot; quantum, i.e. something that should be ensured
to be fulfilled once this &lt;em&gt;task&lt;/em&gt; is done.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;tasks use &lt;em&gt;modules&lt;/em&gt;, which do the actual grunt work and can be implemented in most languages (a lot of them use Python,
obviously since Ansible is written in that). There exists a cornucopia of Ansible built-in modules, from user
 management through ensuring a given is in a file, to EC2 instance setup.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;tasks can be grouped into &lt;em&gt;roles&lt;/em&gt;, which can also contain common variables, custom modules etc..&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;task and role mixes are codified into &lt;em&gt;playbooks&lt;/em&gt;, which describes what your actual configuration should look like.
You &quot;run&quot; the playbooks on your &quot;inventory&quot;&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_2&quot; class=&quot;footnote&quot; href=&quot;#_footnote_2&quot; title=&quot;View footnote.&quot;&gt;2&lt;/a&gt;]&lt;/sup&gt;
to achieve that desired state.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;sidebarblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;div class=&quot;title&quot;&gt;Creating roles&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;By the way, if you&amp;#8217;ve dug through the role documentation and wondered how to create the role scaffolding automatically,
here&amp;#8217;s how you can do that:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;literalblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;ansible-galaxy init &amp;lt;rolename&amp;gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This is of course mentioned only in the Ansible Galaxy documentation, later on.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;You&amp;#8217;ll notice that such a structure allows you to modularize your configuration management logic as you see fit. That
includes creating several &quot;internal&quot; modules for complex actions and sticking them into your
roles.&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_3&quot; class=&quot;footnote&quot; href=&quot;#_footnote_3&quot; title=&quot;View footnote.&quot;&gt;3&lt;/a&gt;]&lt;/sup&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;bad_but_no_code_reuse_for_python_modules&quot;&gt;Bad: &amp;#8230;&amp;#8203;but no code reuse for Python modules&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Funilly enough, due to how Ansible is structured,
&lt;a href=&quot;http://stackoverflow.com/questions/15816952/can-a-developed-ansible-module-include-or-extend-an-ansible-core-module&quot;&gt;you cannot have &quot;common&quot; code files for Python-based modules&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;That sucks, but is probably an edge case for most stuff.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;good_good_introductory_documentation&quot;&gt;Good: Good introductory documentation&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The &lt;a href=&quot;http://docs.ansible.com/intro.html&quot;&gt;docs&lt;/a&gt; &lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_4&quot; class=&quot;footnote&quot; href=&quot;#_footnote_4&quot; title=&quot;View footnote.&quot;&gt;4&lt;/a&gt;]&lt;/sup&gt; do a very nice job of showing you the ropes. They are very much example-based,
 and subsequent steps build upon previous knowledge in a logical way.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;ugly_with_no_formal_sections&quot;&gt;Ugly: &amp;#8230;&amp;#8203;with no formal sections&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;I fail to see, in the documentation, a &quot;Big Picture&quot; overview of how the various components of an Ansible definition are
structured.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Apparently, looking at the the latest &lt;a href=&quot;http://stackoverflow.com/research/developer-survey-2015#profile-education&quot;&gt;Stack Overflow poll&lt;/a&gt;,
I&amp;#8217;m in the minority when it comes to education and this may skew my perception, but  I would give my right spleen for
an &lt;a href=&quot;https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_Form&quot;&gt;EBNF&lt;/a&gt; or similarly-formatted formal specification of the Playbook syntax.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;good_a_globally_shared_role_repository&quot;&gt;Good: A globally shared role repository&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ansible has something called &lt;a href=&quot;https://galaxy.ansible.com/&quot;&gt;Ansible Galaxy&lt;/a&gt;, which is its way to share you common configuration
functionality.&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_5&quot; class=&quot;footnote&quot; href=&quot;#_footnote_5&quot; title=&quot;View footnote.&quot;&gt;5&lt;/a&gt;]&lt;/sup&gt;. It&amp;#8217;s pretty easy to use if you just want to find something (the thing I made is &lt;a href=&quot;https://galaxy.ansible.com/list#/roles/3348&quot;&gt;also there&lt;/a&gt;).&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;ugly_very_nascent_in_the_current_state&quot;&gt;Ugly: &amp;#8230;&amp;#8203;very nascent in the current state&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;However, especially if you display perfectionist tendencies &lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_6&quot; class=&quot;footnote&quot; href=&quot;#_footnote_6&quot; title=&quot;View footnote.&quot;&gt;6&lt;/a&gt;]&lt;/sup&gt;, you will spend quite a bit of time examining the roles
for the functionality your require.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;One problem is that, while a rating system exists, it&amp;#8217;s severely underused, and you have to fish for the well-made roles.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Another is the presence of very clearly clashing philosophies when designing roles. It&amp;#8217;s best demonstrated when comparing
ansible roles from &lt;a href=&quot;https://github.com/geerlingguy&quot;&gt;Jeff Geerling&lt;/a&gt; and the &lt;a href=&quot;https://github.com/Stouts&quot;&gt;Stouts group&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The former offer a definitely correctly functioning role, but make a metric f-ton of assumption about what a given piece
of setup software is going to operate as. The latter, meanwhile, while not being entirely correct idempotency-wise, allow
for very diverse configuration variants.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;To be honest, I came to prefer the Stouts roles for their ability to set up as &lt;strong&gt;I&lt;/strong&gt; want them over the &lt;code&gt;geerlingguy&lt;/code&gt; ones,
despite Mr. Geerling&amp;#8217;s &lt;a href=&quot;https://leanpub.com/ansible-for-devops&quot;&gt;foray into book writing on Ansible&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_7&quot; class=&quot;footnote&quot; href=&quot;#_footnote_7&quot; title=&quot;View footnote.&quot;&gt;7&lt;/a&gt;]&lt;/sup&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;overall&quot;&gt;Overall&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The general picture that I&amp;#8217;ve painted hopefully shows a framework with a number of nits that you can pick, but built
on solid foundations nevertheless. Those solid foundations will provide a payoff as the framework grows and matures,
eliminating the smaller problems along the away. Be aware of the shortcomings, but rest assured that I recommend you check
out Ansible for you configuration management needs.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_1&quot;&gt;
&lt;a href=&quot;#_footnoteref_1&quot;&gt;1&lt;/a&gt;. Of course, for someone working primary in Python, this may not be a problem,  due to their probable contact with and prior use of Jinja2.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_2&quot;&gt;
&lt;a href=&quot;#_footnoteref_2&quot;&gt;2&lt;/a&gt;. A list of target servers with some labelling and variables.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_3&quot;&gt;
&lt;a href=&quot;#_footnoteref_3&quot;&gt;3&lt;/a&gt;. Normally, modules are quite a &quot;big thing&quot;, able to be shared stand-alone, but sometimes the convenience  of writing in an actual Turing-complete programming language is too great to miss.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_4&quot;&gt;
&lt;a href=&quot;#_footnoteref_4&quot;&gt;4&lt;/a&gt;. &amp;#8230;&amp;#8203;which admittedly took a nearly-non-trivial amount of time for me to fish out  out of the sales-pitch-filled landing page&amp;#8230;&amp;#8203;.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_5&quot;&gt;
&lt;a href=&quot;#_footnoteref_5&quot;&gt;5&lt;/a&gt;. If you&amp;#8217;ve never used configuration management software, think of it as the equivalent of the Docker Hub Repository.
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_6&quot;&gt;
&lt;a href=&quot;#_footnoteref_6&quot;&gt;6&lt;/a&gt;. like Yours Truly
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; id=&quot;_footnote_7&quot;&gt;
&lt;a href=&quot;#_footnoteref_7&quot;&gt;7&lt;/a&gt;. Can&amp;#8217;t comment on the book&amp;#8217;s quality, but the roles show good craftsmanship.&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Comments added</title>
      <link>https://xn--mikoak-6db.net/blog/2014/comments.html</link>
      <pubDate>Sat, 6 Sep 2014 00:00:00 +0200</pubDate>
      <guid isPermaLink="false">blog/2014/comments.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Against my better judgment I have added commenting functionality to the site.
Hopefully there won&amp;#8217;t be much spam - went with &lt;a href=&quot;http://intensedebate.com&quot;&gt;IntenseDebate&lt;/a&gt; for
this reason. It supposedly provides better built-in spam-fighting capability.&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>
    <item>
      <title>Frist post</title>
      <link>https://xn--mikoak-6db.net/blog/2014/frist.html</link>
      <pubDate>Sun, 31 Aug 2014 00:00:00 +0200</pubDate>
      <guid isPermaLink="false">blog/2014/frist.html</guid>
      	<description>
	&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Time for an inaugural post. Regarding the present and future contents, there&amp;#8217;s more info on the &lt;a href=&quot;http://miki.org.pl/about.html&quot;&gt;About page&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As you maybe noticed by the header, this page is generated with &lt;a href=&quot;http://www.jbake.org&quot;&gt;JBake&lt;/a&gt;. It&amp;#8217;s also taking advantage of
the JBake Gradle plugin by Cédric Champeau, &lt;a href=&quot;https://melix.github.io/blog/2014/02/hosting-jbake-github.html&quot;&gt;introduced here&lt;/a&gt;
(incidentally also reminding me to add a &lt;a href=&quot;http://creativecommons.org&quot;&gt;Creative Commons&lt;/a&gt; license declaration).&lt;/p&gt;
&lt;/div&gt;
	</description>
    </item>

  </channel> 
</rss>
