<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Libove Blog</title>
    <link>https://blog.libove.org</link>
    <atom:link href="https://blog.libove.org/index.xml" rel="self" type="application/rss+xml"></atom:link>
    <description><![CDATA[Personal Blog about anything - mostly programming, cooking and random thoughts]]></description>
    <pubDate>Thu, 23 Apr 2026 02:53:10 +0000</pubDate>
    <lastBuildDate>Thu, 23 Apr 2026 02:53:10 +0000</lastBuildDate>
    <generator>owl-blogs</generator>
    <item>
      <guid>https://blog.libove.org/posts/container-ship/</guid>
      <title>Container Ship</title>
      <link>https://blog.libove.org/posts/container-ship/</link>
      <pubDate>Wed, 25 Mar 2026 17:15:23 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC02816.jpg" class="u-photo">
    <img src="/thumbnail/DSC02816.jpg" alt="Container Ship" />
</a>

<p><span class="hashtag"><a class="p-category" href="/tags/photography/">#photography</a></span> <span class="hashtag"><a class="p-category" href="/tags/ship/">#ship</a></span></p>


]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/into-the-blue/</guid>
      <title>Into the Blue</title>
      <link>https://blog.libove.org/posts/into-the-blue/</link>
      <pubDate>Wed, 25 Mar 2026 17:14:24 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC02903.jpg" class="u-photo">
    <img src="/thumbnail/DSC02903.jpg" alt="Into the Blue" />
</a>

<p><span class="hashtag"><a class="p-category" href="/tags/bird/">#bird</a></span> <span class="hashtag"><a class="p-category" href="/tags/photography/">#photography</a></span> <span class="hashtag"><a class="p-category" href="/tags/nature/">#nature</a></span> <span class="hashtag"><a class="p-category" href="/tags/sky/">#sky</a></span> <span class="hashtag"><a class="p-category" href="/tags/blue/">#blue</a></span></p>


]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/implementing-animations-with-gltf/</guid>
      <title>Implementing Animations with GLTF</title>
      <link>https://blog.libove.org/posts/implementing-animations-with-gltf/</link>
      <pubDate>Wed, 25 Feb 2026 19:50:46 +0000</pubDate>
      <description><![CDATA[<p>My current hobby project turned from <a href="https://blog.libove.org/posts/61110d41-1e98-4a40-ac42-ff715ebf7e84/">procedural geometry generation</a> to game development.
The current idea is to use the procedural generations I've created so far to build a library where the player can interact with the books. For this I need an animated book model which can be opened and closed.</p>
<p><a href="/media/animation.gif"><img src="/thumbnail/animation.gif" alt="Animation of a book being opened. This was my debug target during the implementation"></a></p>
<p>I've created a simple model and animation and blender and exported these in the <a href="https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html">GLTF</a> format. In my &quot;game engine&quot; I've implemented a GLTF parser from scratch and added an animation system to the rendering. I'll assume your are familiar with the basic concept of joint/bone based animation and linear algebra for gamedev.</p>
<h2>Parsing GLTF</h2>
<p>In my implementation, I've only cared about the binary file format <code>.glb</code> of the GLTF standard.
In this format, meta and binary data are stored in the same file, which I find more convenient for my use case.
Alternatively the data can also be split into the meta data <code>.gltf</code> file and (multiple) binary files <code>.bin</code>.</p>
<p>The meta data in GLTF is stored in a JSON format.
The format specifies different object types which are hierarchically organized in scenes as nodes.
References between objects are generally given as indexes into the corresponding object array within the JSON data.</p>
<p>Most objects will eventually reference to accessor objects.
These describe in which format and where in the binary data values are stored.</p>
<h2>Reading Meshes</h2>
<p>I won't go into too much detail about loading meshes from GLTF, as this isn't the focus of this article.
Meshes have a list of primitives which define the geometry.
Each primitive has a set of attributes, which are accessor indexes for different attributes of vertices, e.g. position and normal data.
Additionally, the primitive object has an <code>indices</code> attribute which points to another accessor.
The indices data indexes into the arrays of the attributes, defining the triangle structure.</p>
<p>For animated meshes it's important to load the <code>JOINTS_0</code> and <code>WEIGHTS_0</code> attributes.
The joints data has to be stored as a 4D integer vector and the weights as 4D floating point vector.
The data will be used at the very end in the vertex shader to apply the joint transformations to the vertices.</p>
<p>Important to note here is that a mesh might have additional <code>JOINTS_n</code> and <code>WEIGHTS_n</code> attributes. This will occur when a single vertex is influenced by more than 4 joints.</p>
<h2>Reading Animation Data</h2>
<p>For animations two object types are important; skins and animations.</p>
<h3>Skins</h3>
<p>Skins define the skeleton used in our animation.
The two attributes we're interested in are <code>joints</code> and <code>inverseBindMatrices</code>.</p>
<p>The <code>joints</code> array holds indexes referring to node objects.
These are the bones of the skeleton.
The hierarchy has to be read from the nodes, which have <code>children</code> attributes.</p>
<p>The <code>inverseBindMatrices</code> attribute is an index to an accessor.
This accessor locates an array of 4x4 matrices in the binary data.
The array must have at least as many matrices as there are joints in the skin object.
These matrices will be needed later to apply the animation to our model</p>
<h3>Animations</h3>
<p>Animations define the transformations which are applied to the skin.
They have two arrays of child objects which define the transformations; <code>channels</code> and <code>samplers</code>.
The channels map attributes of joints to samplers.</p>
<p>The example below says, that the sampler <code>0</code> describes the translation part of the animation for joint <code>2</code>. Each joint can have a channel for each different transforms: translation, rotation and scale. (There is also &quot;weights&quot; but I've ignored this as it only applies to morph targets)</p>
<pre><code class="language-json">{
    &quot;sampler&quot;:0,
    &quot;target&quot;:{
        &quot;node&quot;:2,
        &quot;path&quot;:&quot;translation&quot;
    }
},
</code></pre>
<p>The samplers have three attributes; <code>input</code>, <code>interpolation</code> and <code>output</code>.
Input and output are again indexes to accessors, which point to arrays of equal length.
The <code>input</code> values are time stamps in the animation, while the <code>output</code> are the values at the corresponding time stamp.
The type of the output differs based on the transformation, while the input is always a scalar.
The <code>interpolation</code> defines how values between <a href="https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#appendix-c-interpolation">sample points should be interpolated</a>.</p>
<h2>Gathering all Data</h2>
<p>In my implementation I've first transformed the data read from the sample into these structs.
An <code>AnimationPoint</code> holds the corresponding input and output values.
An <code>AnimationSequence</code> is a vector for points + the interpolation method.
An <code>AnimationSet</code> gathers all sequences for the different transformations of a single joint.</p>
<pre><code class="language-rust">pub struct AnimationPoint&lt;T&gt; {
    pub input: f32,
    pub output: T,
}

pub struct AnimationSequence&lt;T&gt; {
    pub points: Vec&lt;AnimationPoint&lt;T&gt;&gt;,
    pub interpolation: GltfSamplerInterpolation,
}

pub struct AnimationSet {
    pub translation: AnimationSequence&lt;Vec3&gt;,
    pub rotation: AnimationSequence&lt;Vec4&gt;,
    pub scale: AnimationSequence&lt;Vec3&gt;,
    pub weights: AnimationSequence&lt;f32&gt;,
}
</code></pre>
<p>A <code>Joint</code> holds it's own animation data, the inverse bind matrix and the indexes of its children.
The child association is important later on to calculate the transformation matrices.
The child indexes here are not the values read from the JSON data but the position of the joint in the animation struct.</p>
<p>The <code>Animation</code> just holds a vector of joints and stores the index of the root joint.
The common root of the skeleton has to be determined by the implementation itself and is not stored in the GLTF data.
However the standard states that the joints of a skin <strong>must</strong> have a common root.</p>
<pre><code class="language-rust">pub struct Joint {
    set: AnimationSet,
    inverse_bind_mat: Mat4,
    children: Vec&lt;usize&gt;,
}

pub struct Animation {
    joints: Vec&lt;Joint&gt;,
    common_root: usize,
}
</code></pre>
<h2>Calculating the Joint transformation</h2>
<p>Now that we have gathered all data required, we can finally do the animation itself.
For this we need to calculate a transformation matrix for each joint.</p>
<p>This part took me the longest time. I still had a vague idea what had to happen from university (10+ years ago) but it still took me multiple sessions to find all errors and missing calculations in my implementation.
<a href="https://lisyarus.github.io/blog/posts/gltf-animation.html">Lisyarus blog post</a> was a great resource as well as the <a href="https://github.com/SaschaWillems/Vulkan/blob/master/examples/gltfskinning/gltfskinning.cpp">Vulkan example by Sascha Willems</a>.</p>
<p>I'm still not very familiar with the terminology but hopefully this explanation will help :).
Each joint has a local matrix which is defined by its translation, rotation and scale at given point in time.
These values have to be read and interpolated from the animation sequence.
The values are then transformed into 4x4 matrix and multiplied <strong>in this exact order</strong> to retrieve the local matrix.</p>
<pre><code>Joint(N, t) = Translation(N, t) * Rotation(N, t) * Scale(N, t)
</code></pre>
<p>Next we have to <strong>include the hierarchy</strong> into our transformations.
Each local matrix only describes how a vertex is transformed in the reference frame of the joint.
But joints themselves are also move be their parents (when the arm moves the hand is also moved).
These transformations also need to be applied to our vertices.
Therefore we need to traverse the hierarchy and apply each local matrix of a parent to its children.
This has to happen recursively, such that each joint &quot;includes&quot; all transformation from the root to itself.</p>
<p>This can be achieved by traversing the tree starting at the common root.
For each child we simply multiply the local matrix with the matrix of the parent <strong>in the correct order</strong>.
By starting at the root we guarantee that the matrix of the parent joint already includes all previous matrices.</p>
<pre><code>Joint(N, t) = Parent(N, t) * Joint(N, t)
</code></pre>
<pre><code class="language-rust">let mut queue = VecDeque::new();
queue.push_back(self.common_root);
while let Some(parent) = queue.pop_front() {
    for child in self.joints[parent].children.iter() {
        joint_mats[*child] = joint_mats[parent] * joint_mats[*child];
        queue.push_back(*child);
    }
}
</code></pre>
<p>Finally we have to <strong>include the inverse bind matrix, again at the correct place</strong>.
I still don't understand 100% how this matrix functions, but it brings the vertex into the correct reference frame before the joint transformations are applied.</p>
<pre><code>Joint(N, t) = Joint(N, t) * InverseBindMatrix(N) 
</code></pre>
<p>After this step we have a set of matrices which can be sent of to the GPU to be used in our vertex shader.</p>
<h2>Applying the Animation</h2>
<p>Now that the hard part is done, we just have to apply the animation to each vertex in the vertex shader.
I've passed the calculated joint matrices via a read-only storage buffer (I'm using <a href="https://github.com/gfx-rs/wgpu">wgpu</a> for rendering).</p>
<p>Each vertex has a <code>vec4&lt;u32&gt;</code> of joints indexes and corresponding weights as <code>vec4&lt;f32&gt;</code>.
To arrive at the final matrix for a joint look up the correct joint matrices and calculated the weighted sum.</p>
<pre><code class="language-wgsl">    var ani_matrix = model.weights[0] * joint_positions[model.joints[0]] +
            model.weights[1] * joint_positions[model.joints[1]] +
            model.weights[2] * joint_positions[model.joints[2]] +
            model.weights[3] * joint_positions[model.joints[3]];

    out.world_pos = model_matrix * ani_matrix * vec4&lt;f32&gt;(model.position, 1.0);
</code></pre>
<h2>Happy Animation?</h2>
<p>If you did everything correctly you should now be able to enjoy your glorious animations.
Otherwise the model will be a distorted mess and you are sent onto an adventure to look for subtle bugs in any of these steps.
Here are some of the errors I had to fix before my animations looked right:</p>
<ul>
<li>Mixing Joint index and Node index numbers when building the hierarchy</li>
<li>Multiplying translation, rotation and scale in the wrong order</li>
<li>Loading matrices from the GLTF binary as row-major instead of column-major</li>
<li>An error in the translation matrix, I've but the translation values into the last row instead of the last column.</li>
<li>Not adding the inverse bind matrix</li>
<li>Adding the inverse bind matrix between each step of the hierarchy</li>
<li>Not using the hierarchy at all</li>
<li>Errors in sampling from the animation sequence</li>
</ul>
<p><span class="hashtag"><a class="p-category" href="/tags/gamedev/">#gamedev</a></span> <span class="hashtag"><a class="p-category" href="/tags/animation/">#animation</a></span> <span class="hashtag"><a class="p-category" href="/tags/rust/">#rust</a></span> <span class="hashtag"><a class="p-category" href="/tags/gltf/">#gltf</a></span> #3d</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/snow-caps/</guid>
      <title>Snow Caps</title>
      <link>https://blog.libove.org/posts/snow-caps/</link>
      <pubDate>Mon, 19 Jan 2026 00:00:00 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC02689.jpg" class="u-photo">
    <img src="/thumbnail/DSC02689.jpg" alt="Snow Caps" />
</a>

<p><span class="hashtag"><a class="p-category" href="/tags/photography/">#photography</a></span> <span class="hashtag"><a class="p-category" href="/tags/nature/">#nature</a></span></p>


]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/kartenhalter-f-r-pinwand/</guid>
      <title>Kartenhalter für Pinwand</title>
      <link>https://blog.libove.org/posts/kartenhalter-f-r-pinwand/</link>
      <pubDate>Sun, 18 Jan 2026 16:33:26 +0000</pubDate>
      <description><![CDATA[<p>Ein Halter für Karteikarten (Größe A6).</p>
<p><a href="/media/DSC02713.jpg"><img src="/thumbnail/DSC02713.jpg" alt="Ein 3D gedruckter Halter mit Karteikarten an einer Pinnwand"></a></p>
<ul>
<li>3D Modell: <a href="/media/Cardholder-Body.3mf">Cardholder-Body.3mf</a></li>
<li>FreeCAD: <a href="/media/Cardholder.FCStd">Cardholder.FCStd</a></li>
</ul>
<p>#3dprinting</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/sperber/</guid>
      <title>Sperber</title>
      <link>https://blog.libove.org/posts/sperber/</link>
      <pubDate>Sun, 18 Jan 2026 15:58:06 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC02605.jpg" class="u-photo">
    <img src="/thumbnail/DSC02605.jpg" alt="Sperber" />
</a>

<p><span class="hashtag"><a class="p-category" href="/tags/bird/">#bird</a></span> <span class="hashtag"><a class="p-category" href="/tags/photography/">#photography</a></span></p>


]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/sonnenblumenkernbrot/</guid>
      <title>Sonnenblumenkernbrot</title>
      <link>https://blog.libove.org/posts/sonnenblumenkernbrot/</link>
      <pubDate>Mon, 05 Jan 2026 19:09:54 +0000</pubDate>
      <description><![CDATA[<small>
    
    Servings: <span class="p-yield">1 Laib</span>
    , 

    

    
    Prep Time: <time class="dt-duration" value="2 Stunden / 1 Tag">
        2 Stunden / 1 Tag
    </time>
    
</small>
<br> <br>

<h2>Ingredients</h2>

<ul>
    
    <li class="p-ingredient">
        200g Dinkelvollkornmehl
    </li>
    
    <li class="p-ingredient">
        200g Weizenmehl 550
    </li>
    
    <li class="p-ingredient">
        150g Sonnenblumenkerne
    </li>
    
    <li class="p-ingredient">
        50g Müsli
    </li>
    
    <li class="p-ingredient">
        15g Salz
    </li>
    
    <li class="p-ingredient">
        ¼ Würfel Hefe
    </li>
    
</ul>

<h2>Instructions</h2>
<p><a href="/media/Sonnenblumenkernbrot.jpg"><img src="/thumbnail/Sonnenblumenkernbrot.jpg" alt="Angeschnittenes Sonnenblumenkernbrot"></a></p>
<p>Ein einfaches Kastenbrot mit Sonnenblumenkernen. Das Rezept ist sehr flexibel. Gehzeiten können verlängert werden, wenn der Teig im Kühlschrank geht. Wenn es schnell gehen muss kann auch mehr Hefe benutzt werden. Ebenso können die Sonnenblumenkerne durch andere Saaten ausgetauscht werden.</p>
<h2>Vorteig</h2>
<ul>
<li>200g Dinkelvollkornmehl</li>
<li>15g Salz</li>
<li>¼ Würfel Hefe</li>
</ul>
<h2>Saaten</h2>
<ul>
<li>150g Sonnenblumenkerne</li>
<li>50g Müsli</li>
<li>150g Wasser</li>
</ul>
<h2>Teig</h2>
<ul>
<li>Vorteig</li>
<li>Saaten</li>
<li>200g Weizenmehl 550</li>
<li>60g Wasser</li>
</ul>
<h1>Zubereitung</h1>
<ul>
<li>Mehl, Hefe und Wasser mit einer Gabel verrühren und in einer zweiten Schüssel Saaten mit Wasser bedecken</li>
<li>Vorteig und Saaten etwa 2 Stunden gehen lassen</li>
<li>Vorteig, Saaten, Weizenmehl und 60g Wasser zu einem weichen Tag kneten. Wassermenge gegebenenfalls anpassen.</li>
<li>Teig über mindestens 2 Stunden gehen lassen und dabei 3 mal falten. Kann auch über Nacht im Kühlschrank gehen.</li>
<li>Laib formen und in eine Backform geben (falls nötig Form mit Backpapier auslegen oder einfetten)</li>
<li>Teig zwei weitere Stunden gehen lassen (oder über Nacht im Kühlschrank). Das Volumen sollte sich for dem Backen etwa verdoppelt haben.</li>
<li>Laib einschneiden und mit etwas Wasser bespritzen</li>
<li>80 Minuten bei 180°C backen ohne vorheizen</li>
<li>Brot aus der Form nehmen und weitere 15 Minuten backen</li>
</ul>
<p><span class="hashtag"><a class="p-category" href="/tags/brot/">#brot</a></span> <span class="hashtag"><a class="p-category" href="/tags/backen/">#backen</a></span> <span class="hashtag"><a class="p-category" href="/tags/dinkel/">#dinkel</a></span> <span class="hashtag"><a class="p-category" href="/tags/weizen/">#weizen</a></span></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/aufh-nger-f-r-lichterketten-mit-batteriepack-/</guid>
      <title>Aufhänger für Lichterketten mit Batteriepack </title>
      <link>https://blog.libove.org/posts/aufh-nger-f-r-lichterketten-mit-batteriepack-/</link>
      <pubDate>Sun, 09 Nov 2025 20:07:10 +0000</pubDate>
      <description><![CDATA[<p>Einfacher Halter für das Batteriepack an einer Lichterkette. Kann ohne Support gedruckt werden.</p>
<p><a href="/media/DSC02327.jpg"><img src="/thumbnail/DSC02327.jpg" alt="Bild Aufhänger mit Batteriepack hängend an einer Lampe"></a></p>
<p><strong>Dateien:</strong></p>
<ul>
<li><a href="/media/Weihnachtaufhaenger.FCStd">Weihnachtaufhaenger.FCStd</a></li>
<li><a href="/media/Weihnachtaufhaenger.3mf">Weihnachtaufhaenger.3mf</a></li>
</ul>
<p>#3dprint <span class="hashtag"><a class="p-category" href="/tags/model/">#model</a></span> <span class="hashtag"><a class="p-category" href="/tags/freecad/">#freecad</a></span></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/engine/</guid>
      <title>Engine</title>
      <link>https://blog.libove.org/posts/engine/</link>
      <pubDate>Sat, 13 Sep 2025 13:47:57 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC01846.jpg" class="u-photo">
    <img src="/thumbnail/DSC01846.jpg" alt="Engine" />
</a>

<p><span class="hashtag"><a class="p-category" href="/tags/photography/">#photography</a></span> <span class="hashtag"><a class="p-category" href="/tags/train/">#train</a></span></p>
<p>Taken at the RTM Musuem in <span class="hashtag"><a class="p-category" href="/tags/ouddorp/">#Ouddorp</a></span></p>


]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/windpower/</guid>
      <title>Windpower</title>
      <link>https://blog.libove.org/posts/windpower/</link>
      <pubDate>Thu, 11 Sep 2025 00:00:00 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC01604.jpg" class="u-photo">
    <img src="/thumbnail/DSC01604.jpg" alt="Windpower" />
</a>

<p><span class="hashtag"><a class="p-category" href="/tags/photography/">#photography</a></span> <span class="hashtag"><a class="p-category" href="/tags/wind/">#wind</a></span> <span class="hashtag"><a class="p-category" href="/tags/sea/">#sea</a></span></p>


]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/scale/</guid>
      <title>Scale</title>
      <link>https://blog.libove.org/posts/scale/</link>
      <pubDate>Wed, 10 Sep 2025 18:54:03 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC01614.jpg" class="u-photo">
    <img src="/thumbnail/DSC01614.jpg" alt="Scale" />
</a>

<p><span class="hashtag"><a class="p-category" href="/tags/photography/">#photography</a></span> <span class="hashtag"><a class="p-category" href="/tags/netherlands/">#netherlands</a></span> <span class="hashtag"><a class="p-category" href="/tags/sea/">#sea</a></span> <span class="hashtag"><a class="p-category" href="/tags/water/">#water</a></span></p>


]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/industry/</guid>
      <title>Industry</title>
      <link>https://blog.libove.org/posts/industry/</link>
      <pubDate>Wed, 10 Sep 2025 18:52:57 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC01649.jpg" class="u-photo">
    <img src="/thumbnail/DSC01649.jpg" alt="Industry" />
</a>

<p><span class="hashtag"><a class="p-category" href="/tags/photography/">#photography</a></span> <span class="hashtag"><a class="p-category" href="/tags/netherlands/">#netherlands</a></span></p>


]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/touch/</guid>
      <title>Touch</title>
      <link>https://blog.libove.org/posts/touch/</link>
      <pubDate>Mon, 08 Sep 2025 08:19:45 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC01407.jpg" class="u-photo">
    <img src="/thumbnail/DSC01407.jpg" alt="Touch" />
</a>

<p><span class="hashtag"><a class="p-category" href="/tags/photography/">#photography</a></span> <span class="hashtag"><a class="p-category" href="/tags/hand/">#hand</a></span> <span class="hashtag"><a class="p-category" href="/tags/bnw/">#bnw</a></span></p>


]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/passing-strings-and-arrays-between-js-and-wasm/</guid>
      <title>Passing Strings and Arrays between JS and WASM</title>
      <link>https://blog.libove.org/posts/passing-strings-and-arrays-between-js-and-wasm/</link>
      <pubDate>Mon, 11 Aug 2025 19:55:44 +0000</pubDate>
      <description><![CDATA[<p><a href="https://wasmbyexample.dev/examples/webassembly-linear-memory/webassembly-linear-memory.assemblyscript.en-us">WASM has a linear memory</a> to pass data between JavaScript (or any other hosting environment) and WASM. Any data which cannot be represented by WASMs data types (32 and 64bit integers and floats) also has to be passed through this memory.</p>
<p>In JavaScript we can access the WASM memory through a DataView object.</p>
<pre><code class="language-js">  WebAssembly.instantiateStreaming(fetch(&quot;lib.wasm&quot;), {}).then(
            (obj) =&gt; {
				const dv = new DataView(obj.instance.exports.memory.buffer)
				//...
			}
</code></pre>
<p>The DataView object allows us to read and write data to the memory. To pass a string to a WASM function we first have to encoded it to bytes and then write these bytes into the memory buffer. <code>setUint8</code> sets the i-th byte of the memory buffer to the provided value.</p>
<pre><code class="language-js">	// encode string in &quot;program&quot; as utf8 and write to WASM memory
	let utf8Encode = new TextEncoder();
	const p_data = utf8Encode.encode(program);
	for (let i = 0; i &lt; p_data.length; i++) {
		dv.setUint8(i, p_data[i])
	}
</code></pre>
<p>The byte position of the written data serves as the &quot;pointer&quot; to the data, that we have to pass to the WASM function call. As the data was written to the start of the memory it will be <code>0</code> in this case.</p>
<p>The function we want to call has this signature (in zig). The example is taken from the <a href="https://chromahack.rerere.org">chromahack code</a> also used in this article about <a href="https://blog.libove.org/posts/building-wasm-library-with-zig/">writing WASM libraries in zig</a></p>
<pre><code class="language-zig">render_point_2(program: [*]const u8, len: usize, result: [*]f32) void
</code></pre>
<p>As we can only pass pointers, not strings directly, we also have to pass the length of our string. Otherwise the called function doesn't knowns how much data has to be read from the linear memory.</p>
<p>The called function calculates a RGB color value. The values are written to the provided <code>result</code> location, therefore we also have to provide a pointer for the result in the call. The first two parameters define the string we pass into the function, start location <code>0</code> and length of <code>p_data.length</code>. The third parameter is the result location. This is set to <code>p_data.length</code> as this is the first unused location in our memory, directly after the string.</p>
<pre><code class="language-js">obj.instance.exports.render_point_2(0, p_data.length, p_data.length);
</code></pre>
<p>After the function call we have to retrieve the data from the memory. In this case we know that the function will always write 3 floating point values. Otherwise the function would also need to return the length of the result.</p>
<p>The data is again retrieved throught the DataView object via the <code>getFloat32</code> function. Note that the address is given as a byte offset and therefore has to be increased by 4 each time (byte size of a float). The second parameter defines the endianness of the read data.</p>
<pre><code class="language-js">const r = dv.getFloat32(p_data.length + 0, true);
const g = dv.getFloat32(p_data.length + 4, true);
const b = dv.getFloat32(p_data.length + 8, true);
</code></pre>
<p><span class="hashtag"><a class="p-category" href="/tags/dev/">#dev</a></span> <span class="hashtag"><a class="p-category" href="/tags/wasm/">#wasm</a></span> <span class="hashtag"><a class="p-category" href="/tags/js/">#js</a></span> <span class="hashtag"><a class="p-category" href="/tags/webdev/">#webdev</a></span></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/building-wasm-library-with-zig/</guid>
      <title>Building WASM library with Zig</title>
      <link>https://blog.libove.org/posts/building-wasm-library-with-zig/</link>
      <pubDate>Mon, 11 Aug 2025 19:06:38 +0000</pubDate>
      <description><![CDATA[<p><em><strong>This is written for Zig 0.14.1.</strong></em></p>
<p>For my <a href="https://chromahack.rerere.org">chromahack</a> project I've used zig to implement the language. To integrate this into an interactive web app I had to compile it to <a href="https://webassembly.org/">WASM</a>.</p>
<h2>Function Definition</h2>
<p>The original function I wanted to export as WASM has this signature:</p>
<pre><code class="language-zig">pub fn render_point(program: []const u8, t: f32, x: f32, y: f32) [3]f32
</code></pre>
<p>This was a problem as the WASM target does not support the types <code>[]const u8</code> and <code>[3]f32</code> as function parameter/return type. Therefore I've created a new function (creatively called <code>render_point_2</code>) to wrap my original implementation:</p>
<pre><code class="language-zig">export fn render_point_2(program: [*]const u8, len: usize, t: f32, x: f32, y: f32, result: [*]f32) void {
    const r = render_point(program[0..len], t, x, y);
    result[0] = r[0];
    result[1] = r[1];
    result[2] = r[2];
}
</code></pre>
<p>The program is now passed as a pointer to an array + its length. The function no longer has a return value, but writes the result to a provided array address. The caller has to ensure that the array can store 3 floating point values.</p>
<p>Also note the <code>export</code> keyword. I've missed this and spent an hour wondering why my WASM file had no exported function.</p>
<h2>Building WASM</h2>
<p>To build the WASM file you can either use the <code>zig build-exe</code> command or integrate it into your <code>build.zig</code> file.</p>
<p>The command <code>zig build-exe src/root.zig -target wasm32-freestanding -fno-entry -rdynamic -OReleaseFast</code> will create the WASM file in your project folder.</p>
<p>To build the WASM file via <code>zig build</code> add the following block to your <code>builg.zig</code> file. This will create the file in <code>zig-out/bin</code>. (based on <a href="https://gist.github.com/travisstaloch/ed5bb79a533f47d9649bd1000bec954f">this gist from trasstaloch</a> )</p>
<pre><code class="language-zig">const wasm = b.addExecutable(.{
	.name = &quot;lib&quot;,
	.root_source_file = b.path(&quot;src/root.zig&quot;),
	.target = b.resolveTargetQuery(std.Target.Query.parse(
		.{ .arch_os_abi = &quot;wasm32-freestanding&quot; },
	) catch unreachable),
	.optimize = optimize,
});
wasm.entry = .disabled;
wasm.rdynamic = true;
b.installArtifact(wasm);
</code></pre>
<p>It is also possible to copy further assets into the output directoy, such as your HTML and JavaScript files.</p>
<pre><code class="language-zig">// copy web stuff
b.installBinFile(&quot;index.html&quot;, &quot;index.html&quot;);
b.installBinFile(&quot;assets/favicon.png&quot;, &quot;favicon.png&quot;);
</code></pre>
<p><span class="hashtag"><a class="p-category" href="/tags/zig/">#zig</a></span> <span class="hashtag"><a class="p-category" href="/tags/dev/">#dev</a></span> <span class="hashtag"><a class="p-category" href="/tags/wasm/">#wasm</a></span></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/chromahack--a-programming-language/</guid>
      <title>Chromahack, a programming language</title>
      <link>https://blog.libove.org/posts/chromahack--a-programming-language/</link>
      <pubDate>Fri, 01 Aug 2025 20:38:20 +0000</pubDate>
      <description><![CDATA[<p><strong>TL;DR:</strong> <a href="https://chromahack.rerere.org/">https://chromahack.rerere.org/</a></p>
<p><a href="/media/logo.png"><img src="/thumbnail/logo.png" alt="chromahack logo"></a></p>
<p>I've created a small programming languages I've been thinking about for a long time. I wanted a programming language that would interpret any string as a valid program and create some visual result out of that. The inspiration was the hacker movie aesthetics, typing furiously on their laptops and bright, colorful patterns flashing over screens.</p>
<p><a href="/media/hack-the-planet.gif"><img src="/thumbnail/hack-the-planet.gif" alt="HACK THE PLANET"></a></p>
<h2>Design</h2>
<p>The language is implemented as a stack based machine. Each character is interpreted as an operation on this stack. For example, <code>a</code> take the top two items from the stack, adds them up and puts the result back. Unassigned symbols are simply ignored.</p>
<p>The stack is initiated with the x and y coordinates of a pixel in an image. (I also put a time value, which is always zero, onto the stack as I want to extend the system for animations in the future).</p>
<p>The program is then executed for each pixel in an [512x512] image. When the program stops (this is guaranteed through the language), the top three items of the stack are interpreted as RGB values.</p>
<p><a href="/media/canvas.png"><img src="/thumbnail/canvas.png" alt="Mandelbrot rendered using chromahack"></a></p>
<h2>Implementation</h2>
<p>The implementation of the languages itself is rather simple, as it's only a large switch-case statement.
As I wasn't sure how fast the interpreter would run when implemented JavaScript I've decided to built it in <a href="https://ziglang.org">zig</a>.</p>
<p>The zig program is split into a library and executable. The executable simply wraps the library by invoking the <code>render_point</code> function for each pixel in an image and saving the result to disk.</p>
<p>Usage via the command line was rather tedious. Therefore I've compiled the library to WASM to integrate it into an web app to allow for an interactive workflow.</p>
<p>As rendering a long program might take a few seconds I've split the rendering process into chunks to avoid blocking the UI. Randomizing the order of chunks added a nice hackeresque feel to the process.</p>
<p>To create the layout of the website I used <a href="https://claude.ai">Claude</a>, as this was a part of the process I was not interested in. Everything else is organic, hand-crafted artisanal code.</p>
<h2>Conclusion and Future</h2>
<p>This was a fun project, which could be implemented within a few programming session. I've struggled a bit generating the WASM file. The zig documentation on this topic is rather slim and many examples are outdated. However, this was the first time I've worked with WASM and in the end it was <a href="https://codeberg.org/h4kor/chromahack/src/commit/f444247a57ad3323fb153f79f2062bef419417ee/build.zig#L120">a simple solution</a>.</p>
<p>In the future I want to implement the animation feature of the language. For this I will also have to increase the performance. I want to try WebGL and SIMD for this. Another interesting topic might be to generate programs through algorithms. I think genetic algorithms could work well with this language as it can be mutated and crossed without concern for validity.</p>
<p><strong>Web Version:</strong> <a href="https://chromahack.rerere.org/">https://chromahack.rerere.org/</a></p>
<p><strong>Source Code:</strong> <a href="https://codeberg.org/h4kor/chromahack">https://codeberg.org/h4kor/chromahack</a></p>
<p><span class="hashtag"><a class="p-category" href="/tags/dev/">#dev</a></span> <span class="hashtag"><a class="p-category" href="/tags/chromahack/">#chromahack</a></span> <span class="hashtag"><a class="p-category" href="/tags/language/">#language</a></span> <span class="hashtag"><a class="p-category" href="/tags/visual/">#visual</a></span></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/sumpfmeise/</guid>
      <title>Sumpfmeise</title>
      <link>https://blog.libove.org/posts/sumpfmeise/</link>
      <pubDate>Mon, 28 Jul 2025 19:07:14 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC01124.jpg" class="u-photo">
    <img src="/thumbnail/DSC01124.jpg" alt="Sumpfmeise" />
</a>

<p><span class="hashtag"><a class="p-category" href="/tags/photography/">#photography</a></span> <span class="hashtag"><a class="p-category" href="/tags/bird/">#bird</a></span> <span class="hashtag"><a class="p-category" href="/tags/nature/">#nature</a></span></p>
<p>Sony A6400 with Tamron 18-300mm</p>


]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/schnelle-asia-pfanne/</guid>
      <title>Schnelle Asia Pfanne</title>
      <link>https://blog.libove.org/posts/schnelle-asia-pfanne/</link>
      <pubDate>Mon, 28 Jul 2025 08:05:26 +0000</pubDate>
      <description><![CDATA[<small>
    
    Servings: <span class="p-yield">2-3 Portionen</span>
    , 

    

    
    Prep Time: <time class="dt-duration" value="20 Minuten">
        20 Minuten
    </time>
    
</small>
<br> <br>

<h2>Ingredients</h2>

<ul>
    
    <li class="p-ingredient">
        200g Udon Nudeln
    </li>
    
    <li class="p-ingredient">
        4 EL Sojasauce
    </li>
    
    <li class="p-ingredient">
        1 EL Reisessig
    </li>
    
    <li class="p-ingredient">
        1 EL Agavendicksaft
    </li>
    
    <li class="p-ingredient">
        2 TL Speisestärke
    </li>
    
    <li class="p-ingredient">
        Gemüse und Sojaschnetzel
    </li>
    
    <li class="p-ingredient">
        1 Stück Ingwer (optional)
    </li>
    
    <li class="p-ingredient">
        1 Knoblauchzehe (optional)
    </li>
    
</ul>

<h2>Instructions</h2>
<p>Dieses Rezept koche ich zur Zeit sehr häufig in meiner Mittagspause. Es ist schnell und einfach und lässt sich endlos variieren.</p>
<ul>
<li>Gemüse länglich, mundgerecht schneiden. Sojaschnetzel aufkochen und abgießen.</li>
<li>Ingwer und Knoblauch fein hacken</li>
<li>Sojasauce, Reisessig, Agavendicksaft mit 200ml Wasser verrühren</li>
<li>Udonnudel nach Packung kochen</li>
<li>Sojaschnetzel und Gemüse anbraten. Zutaten je nach Garzeit später hinzugeben.</li>
<li>Ingwer und Knoblauch 1 Minute mit anbraten und mit Sauce ablöschen</li>
<li>Speisestärke in etwas Wasser auflösen und unterrühren</li>
<li>fertige Nudeln abgießen und zur Sauce hinzufügen</li>
</ul>
<h3>Beliebte Gemüse Kombination</h3>
<ul>
<li>Sojaschnetzel und TK Erbsen</li>
<li>Sojaschnetzel und Zucchini</li>
<li>Möhren und TK Erbsen</li>
<li>Paprika und Zwiebeln</li>
</ul>
<p><span class="hashtag"><a class="p-category" href="/tags/rezept/">#rezept</a></span> <span class="hashtag"><a class="p-category" href="/tags/schnell/">#schnell</a></span> <span class="hashtag"><a class="p-category" href="/tags/vegan/">#vegan</a></span></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/ki-chatkontrolle/</guid>
      <title>KI Chatkontrolle</title>
      <link>https://blog.libove.org/posts/ki-chatkontrolle/</link>
      <pubDate>Thu, 24 Jul 2025 18:33:10 +0000</pubDate>
      <description><![CDATA[<p>Mir fällt gerade auf, dass durch Integration von &quot;KI Tools&quot; Auf Betriebssystem Ebene die Infrastruktur geschaffen wird, die für die Chatkontrolle gefordert wurde.</p>
<h2>Chatkontrolle</h2>
<p>Da fast alle gängigen Messenger inzwischen Ende zu Ende verschlüsselt kommunizieren haben Sicherheitsbehörden es schwer Chats zu überwachen. Da auch die Betreiber der Chatprogramme keinen Einblick haben ist die einzige verbleibende Möglichkeit zu Überwachung das abfangen der Nachrichten auf dem Gerät der Nutzer. Dies benötigt zur Zeit den Einsatz eines &quot;Staatstrojaners&quot;.</p>
<p>Da der Trojanereinsatz ein schwerer und kostspieliger Eingriff ist, versucht die Politik immer wieder eine Alternative zu schaffen. Der letzte Angriff war die sogenannten <a href="https://freiheitsrechte.org/themen/freiheit-im-digitalen/chatkontrolle">Chatkontrolle</a>. Unter dem Vorwand der Kindesmissbrauchsbekämpfung sollen Chatanbieter gezwungen werden Chats nach dokumentiertem Kindesmissbrauch zu durchsuchen.</p>
<p>Technisch bedeuten diese Versuche, dass die Ende-zu-Ende Verschlüssung entweder entfernt oder umgangen werden muss. Anbieter könnten die Verschlüsselung anpassen um Daten auf ihren Servern zu analysieren oder alternative über einen zweiten Kanal zur Analyse ausspielen.</p>
<h2>KI Tools</h2>
<p>Im aktuellen GenAI Hype wird in derzeit in jede App ein &quot;KI Funktion&quot; eingebaut die nicht bei drei auf den Bäumen ist. Die Giganten Google and Microsoft gehen direkt einen Schritt weiter und bauen die KI direkt in ihre Betriebssystem ein.</p>
<p>Bei Microsoft heißt das &quot;Feature&quot; <a href="https://www.apple.com/apple-intelligence/">Recall</a> und ist standardmäßig Teil von Windows 11. Recall macht alle 30 Sekunden einen Screenshot und speichert diesen in einer Datenbank. Die KI kann diese Datenbank durchsuchen und dem Nutzer so helfen Sachen wiederzufinden. Die Verarbeitung geschieht laut Microsoft ausschliesslich lokal auf dem eigenen Rechner.</p>
<p>Google integriert zur Zeit ihr <a href="https://tuta.com/de/blog/how-to-disable-gemini-on-android">KI System Gemini tief in das Android Betriebssystem</a>. Es soll es der Benutzerin ermöglichen die KI über alle Apps hinweg zu benutzen. Ohne das die App selbst KI Funktionen implementiert. Dies bedeutet jedoch auch, dass Gemini auf die Daten in allen Apps zugreifen kann. Da die Daten &quot;in der Cloud&quot; verarbeitet werden, erlangt Google somit Zugriff auf Chats und Daten die vorher privat waren.</p>
<p>(Mit Apples Plänen habe ich mich nicht auseinander gesetzt, aber auch <a href="https://www.apple.com/apple-intelligence/">hier wird im AI Game mitgespielt</a>)</p>
<p>Die Entwicklung von KI Modellen benötigt massiv Daten. Aktuelle Modelle haben bereits alle frei zugänglichen Quellen leer gesaugt und benötigen Nachschub. Auch wenn die Systeme zur Zeit nicht genutzt werden um Trainingsdaten zu generieren, ist es nicht abwegig, dass sich dies schnell ändern wird. Im Wettkampf um die besten Modelle werden immer mehr Daten benötigt. Und Daten die Nutzerverhalten abbilden sind nicht erst im aktuellen KI Hype wertvoll geworden.</p>
<p>Auch OpenAI will Daten durch einen <a href="https://www.reuters.com/business/media-telecom/openai-release-web-browser-challenge-google-chrome-2025-07-09/">eigenen Browser</a> Daten sammeln. Meta hatte noch nie ein augeprägtes Verständnis für Datenschutz und sammelt mit <a href="https://netzpolitik.org/2025/meta-ki-jetzt-widersprechen-oder-fuer-immer-schweigen/">Facebook und Instagram</a> fleißig mit.</p>
<h2>Nur ein Schritt fehlt</h2>
<p>Die Infrastruktur die man zum sammeln von KI Trainingsdaten und zur Überwachung ala Chatkontrolle benötigt unterscheidet sich nur in wenigen Details. In beiden Fällen benötigt man Funktionen die auf den Geräten der Nutzer Daten sammeln. Diese Daten müssen dann ausgeleitet werden um sie auf den eigenen Servern zu verarbeiten. Der Unterschied zwischen KI und Chatkontrolle ist am Ende nur <strong>was</strong> man mit diesen Daten machen will.</p>
<p>Die Chatkontrolle ist zum Glück vorest gescheitert, aber die Tech-Konzerne bauen zur Zeit Infrastruktur die sich leicht anpassen lassen kann um diese umzusetzen. Recall kann die Screenshots nebenbei auch auf potenziell illegale Inhalte untersuchen und melden. Bei Gemini werden die Daten direkt von Google verarbeitet und neben der AI kann auch noch ein zweites System Analysen durchführen.</p>
<p>Die so entstehenden Daten-Silos werden auch ein begehrtes Ziel für die Sicherheitsbehörden werden. Da die Infrastruktur beim nächsten Versuch eine Überwachung einzuführen quasi schon existiert, wird die Abwehr diese Begehren deutlich schwieriger werden.</p>
<p><span class="hashtag"><a class="p-category" href="/tags/politik/">#politik</a></span> <span class="hashtag"><a class="p-category" href="/tags/ki/">#ki</a></span> <span class="hashtag"><a class="p-category" href="/tags/%C3%BCberwachung/">#überwachung</a></span> <span class="hashtag"><a class="p-category" href="/tags/datenschutz/">#datenschutz</a></span></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/re--wikipedia-to-follow-new-album-releases/</guid>
      <title>Re: Wikipedia to follow new album releases</title>
      <link>https://blog.libove.org/posts/re--wikipedia-to-follow-new-album-releases/</link>
      <pubDate>Sun, 20 Jul 2025 18:20:16 +0000</pubDate>
      <description><![CDATA[Reply to: <a href="https://jamesg.blog/2025/07/15/brainstorming-a-tool-to-follow-new-album-releases-with-wikipedia">https://jamesg.blog/2025/07/15/brainstorming-a-tool-to-follow-new-album-releases-with-wikipedia</a>

<p>You don't need to parse the data from the Wikipedia page, it is also available in Wikidata. However the data is not (always?) the same as in Wikipedia. For the example below the latest album was not linked yet (fixed now).</p>
<p><a href="https://query.wikidata.org/#SELECT%20%3Falbum%20%3FperformerLabel%20%3FalbumLabel%20%3Fpublication_date%20WHERE%20%7B%0A%20%20VALUES%20%3Fperformer%20%7B%0A%20%20%20%20%20%20wd%3AQ246352%0A%20%20%20%20%7D%0A%20%20%20%3Falbum%20wdt%3AP175%20%3Fperformer%20.%0A%20%20%20%3Falbum%20wdt%3AP31%20wd%3AQ482994%20.%0A%20%20%20%3Falbum%20wdt%3AP577%20%3Fpublication_date%0A%20%20%20SERVICE%20wikibase%3Alabel%20%7B%20bd%3AserviceParam%20wikibase%3Alanguage%20%22en%22.%20%7D%0A%7D">Example for Rise Against</a></p>
<pre><code>SELECT ?album ?performerLabel ?albumLabel ?publication_date WHERE {
  VALUES ?performer {
      wd:Q246352
    }
   ?album wdt:P175 ?performer .
   ?album wdt:P31 wd:Q482994 .
   ?album wdt:P577 ?publication_date
   SERVICE wikibase:label { bd:serviceParam wikibase:language &quot;en&quot;. }
}
</code></pre>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/re--origin-of-link-router-pattern-/</guid>
      <title>Re: Origin of link router pattern </title>
      <link>https://blog.libove.org/posts/re--origin-of-link-router-pattern-/</link>
      <pubDate>Sun, 20 Jul 2025 15:13:34 +0000</pubDate>
      <description><![CDATA[Reply to: <a href="https://jamesg.blog/2025/07/19/link-router-pattern">https://jamesg.blog/2025/07/19/link-router-pattern</a>

<p>I think the origin, or popularity, of services such as LinkTree is the artificial limitations of links in social networks. Instagram does not allow links in post and you can only have a single link in your profile. This makes it hard to promote a song or to link to multiple other channels.</p>
<p>Other platforms, such as LinkedIn, are suspected to limit the reach of articles if they include several links. Therefore people use an indirection through a router to keep links to a minimal.</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/implementing-my-own-time-series-database--core-structure--part-1-/</guid>
      <title>Implementing my own Time Series Database: Core Structure (Part 1)</title>
      <link>https://blog.libove.org/posts/implementing-my-own-time-series-database--core-structure--part-1-/</link>
      <pubDate>Fri, 11 Jul 2025 19:12:45 +0000</pubDate>
      <description><![CDATA[<p>I'm currently working a lot with time series data and databases. That sparked an interest in looking under the hood and understand database systems more deeply. Fueled by &quot;how hard can this actually be&quot;-ignorance I've started to implement my own time series database. Because learning one new thing wasn't enough, I also learned <a href="https://ziglang.org/">zig</a> in parallel.</p>
<p>I've started the journey by binge-watching the <a href="https://www.youtube.com/watch?v=APqWIjtzNGE">CMU Intro to Database Systems</a> lectures. Andy Pavlo is an amazing lecturer, and I can only recommend this series to anyone wanting to understand DB systems or being nostalgic about their time at university.</p>
<p>My goal was to build a DB that could:</p>
<ul>
<li>store time series identified by an ID</li>
<li>query and transform these series using a simple query language</li>
<li>be accessible over the network</li>
</ul>
<h2>Core Structure</h2>
<p>I've started my journey by figuring out to work with pages. This finally resulted in the following overall architecture for the core of my database.</p>
<p>A page directory serves as the central component handling pages in memory. Any other part of the system will retrieve and interact with pages through this directory. The directory itself is only responsible for pages in memory. Whenever it needs to persist or read pages from disk, it will use the File Manager.</p>
<p>The file manager is the single structure interfacing with the file system. Its only responsibility is to persist pages to disk and retrieve them again later on. Because to the layout of files I've chosen it knows some details about the database implementation, but could be refactored to be completely agnostic of the database using it.</p>
<p>The main user of the page directory is the B+Tree implementation. Series in the database system are represented by B+Trees. Other systems will instantiate B+Tree instances for each series they interact with. The tree structure loads necessary data from the series pages via the page directory.</p>
<h2>Pages</h2>
<p>Pages are the core entities of database systems. As databases have to deal with (much) more data than will fit into memory data has to be read and written to disk frequently. Therefore, all persistent data is structured into pages. In essence, pages are just data blocks of a specified size. These can be moved between disk and memory without transformations.</p>
<p>In my system, the start of each page is used as a page header. The header specifies what kind of data is stored in the page. Additionally, it has a version number for future use and a CRC hash to detect data corruption. The body of a page is just a block of data, which structure depends on its type. The interpretation of the body is controlled by the user of the page.</p>
<p>As pages are transferred between disk and memory, it is important to have exact control over their layout. Regular <code>struct</code> have no guarantees on the layout of fields in memory and have additional padding bytes between fields for alignment. Therefore, I used <a href="https://zig.guide/working-with-c/packed-structs/"><code>packed struct</code></a> for all page definitions.</p>
<p>The size of a page is controlled by a comptime variable <code>PAGE_SIZE</code>. Thanks to zig's comptime feature, all related sizes and offsets can be derived from this and compile time. I can also derive capacity of the B+Tree branches and leaves (see below) from this. Therefore, I can simply adjust the page size, and all other parts will adjust accordingly.</p>
<pre><code>/// total size of pages in bytes
pub const PAGE_SIZE: usize = 4 * 1024;
/// size of page header in bytes
pub const PAGE_HEADER_SIZE: usize = @bitSizeOf(PageHeader) / 8;
/// size of page data section in byters
pub const PAGE_DATA_SIZE: usize = PAGE_SIZE - PAGE_HEADER_SIZE;
</code></pre>
<p>One limitation I ran into is that zig <a href="https://github.com/ziglang/zig/issues/12547">does not (yet) allow arrays in packed structs</a> . Because of this limitation, I had to define an integer type of the correct size to specify the body of the page, instead of simply using <code>[BODY_SIZE]u8</code>. Another alternative would have been <code>@Vector(BODY_SIZE, u8)</code> (as found in the issue), but this felt more wrong.</p>
<pre><code class="language-zig">pub const PageHeader = packed struct {
    type: PAGE_TYPE,
    version: u8,
    crc: u32 = 0,
};

pub const PageData = @Type(.{ 
	.int = .{
		.signedness = .unsigned,
		.bits = PAGE_DATA_SIZE * 8
	}
});

pub const Page = packed struct {
    header: PageHeader,
    // data is only used as a blob of data, and not directly used
    data: PageData,
}
</code></pre>
<h2>File Structure</h2>
<p>After implementing my page structure, I had to think about how to organize them on disk. I've decided to store each series created in the database as an individual file. Pages within a file/series are identified by there position within the file. Thereby, it was easy to get any page for a particular series by the file name. An additional hope was that this would keep the related data close in the filesystem, improving performance, but I never tested this hypothesis.</p>
<p>This structure means that pages have two IDs; a local and a global ID. The local ID is the page's position within a series file. The global ID is the concatenation of series ID and the local page ID. The first page (local ID <code>0</code>) of each file is a series header page. This page keeps information about the series. At the moment it only stores the ID of the root pages for the B+Tree.</p>
<h2>Page Directory</h2>
<p>The page directory is responsible for holding and managing pages in memory. On initialization, it allocates a fixed number of slots which can hold pages. Thereby, no memory has to be allocated when a new page is loaded from disk and the footprint of the directory is static. For each slot a <code>PageHandle</code> handle is created. These handles track usage of the slot; which pages is currently loaded, whether it has been modified or if it is currently in use.</p>
<p>The interface for the using systems is rather simple. Pages can be requested by there ID, either in read or write mode. The page directory transparently loads them from disk, through the file manager, and provides the corresponding page handle to the caller. Once the page is no longer needed the page handle is &quot;returned&quot; to the directory.</p>
<p>Eventually, during the runtime of the system, enough pages will have been loaded that all pages are occupied. At this point, a currently unused page has to be evicted. Each page handle has a <code>used</code> flag, which is set whenever anyone uses the page. When the directory looks for a free slot it iterates over the slots and skips any slot with a raised used flag and unsets it. Once it encounters a slot with an unset flag which is currently not locked the pages is replaced. <a href="https://www.youtube.com/watch?v=aoewwZwVmv4">This approach approximates &quot;least recently used&quot;</a>, but is much faster than a full implementation.</p>
<p>Modified pages are not directly saved to disk when they are freed by a writing user. Instead they are kept dirty in the directory and only written to disk when the page has to be evicted. To prevent data loss, I've added a background worker with cycles through all slots to persist dirty pages if they are currently not in use. (A lesson learnt after losing a few weeks of data which was never persists.)</p>
<h3>B+Tree</h3>
<p>I chose to represent series as B+Trees, a.k.a. the best <a href="https://www.youtube.com/watch?v=scUtG_6M_lU">data structure in computer science</a>. In contrast to other time series databases, which often use <a href="https://en.wikipedia.org/wiki/Log-structured_merge-tree">LSM trees</a> I chose to use B+Tree as it keeps the data ordered at every point in time. This makes traversal of the series data simple and (hopefully) fast.</p>
<p>B+Trees consist of branch and leaf nodes. Branch nodes keep a list of references to their children. Between two child references the branch keeps a threshold values. The referenced child between two threshold values will only hold values between these values. The rules by which the tree is transformed when values are added or removed keep it balanced, where a leaf nodes are at the same depth. This allows lookup of values within a few steps through the tree.</p>
<p>In my database the leaf nodes store tuples of <code>(timestamp, value)</code> in order. Additionally they hold a reference to their next sibling. This allows fast traversal of the leaf nodes when scanning over a time period.</p>
<p><strong>Next Part:</strong> Query Language</p>
<p><span class="hashtag"><a class="p-category" href="/tags/db/">#db</a></span> <span class="hashtag"><a class="p-category" href="/tags/database/">#database</a></span> <span class="hashtag"><a class="p-category" href="/tags/tsdb/">#tsdb</a></span> <span class="hashtag"><a class="p-category" href="/tags/dragondb/">#dragondb</a></span> <span class="hashtag"><a class="p-category" href="/tags/zig/">#zig</a></span> <span class="hashtag"><a class="p-category" href="/tags/dev/">#dev</a></span></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/dinkelvollkornbrot-mit-k-rnern/</guid>
      <title>Dinkelvollkornbrot mit Körnern</title>
      <link>https://blog.libove.org/posts/dinkelvollkornbrot-mit-k-rnern/</link>
      <pubDate>Thu, 01 May 2025 17:38:11 +0000</pubDate>
      <description><![CDATA[<small>
    
    Servings: <span class="p-yield">1 Laib</span>
    , 

    

    
    Prep Time: <time class="dt-duration" value="24 Stunden, 15 Minuten Arbeitszeit">
        24 Stunden, 15 Minuten Arbeitszeit
    </time>
    
</small>
<br> <br>

<h2>Ingredients</h2>

<ul>
    
    <li class="p-ingredient">
        600g Dinkelvollkornmehl
    </li>
    
    <li class="p-ingredient">
        200g Sonnenblumkerne
    </li>
    
    <li class="p-ingredient">
        100g Basis-Müsli
    </li>
    
    <li class="p-ingredient">
        6g Hefe
    </li>
    
    <li class="p-ingredient">
        15g Salz
    </li>
    
    <li class="p-ingredient">
        720g Wasser
    </li>
    
</ul>

<h2>Instructions</h2>
<p>Das Rezept ist eine vereinfachte Variation von <a href="https://blog.libove.org/posts/dinkelvollkornbrot-mit-nssen/">Dinkelvollkornbrot mit Nüssen</a>. Die Saaten können nach belieben angepasst werden.</p>
<h3>Vorteig</h3>
<ul>
<li>300g Dinkelvollkornmehl</li>
<li>300g Wasser</li>
<li>15g Salz</li>
</ul>
<h3>Saaten</h3>
<ul>
<li>200g Sonnenblumkerne</li>
<li>100g Basismüsli</li>
<li>300g Wasser</li>
</ul>
<h3>Hauptteig</h3>
<ul>
<li>Vorteig</li>
<li>Saaten</li>
<li>300g Dinkelvollkornmehl</li>
<li>120g Wasser</li>
<li>6g Hefe</li>
</ul>
<h2>Zubereitung</h2>
<ul>
<li>Vorteig und Saaten in zwei Schüsseln ansetzten und mindestens 2 Stunden gehen lassen</li>
<li>Alle Zutaten in einer großen Schlüssel verkneten bis homogen</li>
<li>Hauptteig 2 Stunden gehen lassen und 3 mal dehnen und falten</li>
<li>Hauptteig falten und zu Laib formen und in eine Brotform geben. Form gegebenfalls mit Backpapier auslegen oder einfetten.</li>
<li>Form mit Alufolie oder Klarsichfolie abdecken</li>
<li>Mindestens 12 Stunden im Kühlschrank gehen lassen. Teig sollte sich etwa im Volumen verdoppeln.</li>
<li>Ofen nicht vorheizen. Brot einschneiden und für 90 Minuten bei 180°C Ober-/Unterhitze backen.</li>
<li>Brot aus Form nehmen und weitere 10 Minuten ohne Form backen</li>
</ul>
<p><span class="hashtag"><a class="p-category" href="/tags/brot/">#brot</a></span> <span class="hashtag"><a class="p-category" href="/tags/dinkel/">#dinkel</a></span> <span class="hashtag"><a class="p-category" href="/tags/backen/">#backen</a></span></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/schnelles-ciabatta/</guid>
      <title>Schnelles Ciabatta</title>
      <link>https://blog.libove.org/posts/schnelles-ciabatta/</link>
      <pubDate>Mon, 21 Apr 2025 18:19:39 +0000</pubDate>
      <description><![CDATA[<small>
    
    Servings: <span class="p-yield">2 Laibe</span>
    , 

    

    
    Prep Time: <time class="dt-duration" value="3 Stunden, 30 Minuten Arbeit">
        3 Stunden, 30 Minuten Arbeit
    </time>
    
</small>
<br> <br>

<h2>Ingredients</h2>

<ul>
    
    <li class="p-ingredient">
        550g Weizenmehl 550
    </li>
    
    <li class="p-ingredient">
        430g Wasser
    </li>
    
    <li class="p-ingredient">
        20g Olivenöl
    </li>
    
    <li class="p-ingredient">
        15g Salz
    </li>
    
    <li class="p-ingredient">
        10g Frische Hefe (oder 1/2 Packung Trockenhefe)
    </li>
    
</ul>

<h2>Instructions</h2>
<p>Schnelles Ciabatta das ich spontan zum grillen gebacken habe. Mengen basieren auf <a href="https://www.ploetzblog.de/rezepte/ciabatta-mit-sauerteig/id=6227ba500243b06a084499b4">Plötzblog: Ciabatta mit Sauerteig</a>.</p>
<ul>
<li>Hefe in Wasser auflösen</li>
<li>Alle Zutaten mischen und kurz kneten</li>
<li>In einer Schüssel gehen lassen und alle 30 Minuten dehnen und falten bis sich das Volumen verdoppelt hat</li>
<li>Ofen auf 250°C vorheizen</li>
<li>Teig auf einer gemehlten Fläche teilen und zu zwei Laiben formen</li>
<li>15-20 Minuten backen</li>
</ul>
<p><span class="hashtag"><a class="p-category" href="/tags/brot/">#brot</a></span> <span class="hashtag"><a class="p-category" href="/tags/backen/">#backen</a></span></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/review--starfish-by-peter-watts/</guid>
      <title>Review: Starfish by Peter Watts</title>
      <link>https://blog.libove.org/posts/review--starfish-by-peter-watts/</link>
      <pubDate>Thu, 03 Apr 2025 19:51:41 +0000</pubDate>
      <description><![CDATA[<data class="p-rating" value="4">★★★★☆</data>
<span class="u-review-of">
    
        <a class="h-item" href="https://www.rifters.com/real/STARFISH.htm">Starfish</a>
    
    by
    
        <span>Peter Watts</span>
    
</span>

<p>Read this as it's from the same author as <a href="https://blog.libove.org/posts/review--blindsight-by-peter-watts/">Blindsight</a>. Has some intriguing elements, but came not close to other novels of Peter Watts i read.</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/review--blindsight-by-peter-watts/</guid>
      <title>Review: Blindsight by Peter Watts</title>
      <link>https://blog.libove.org/posts/review--blindsight-by-peter-watts/</link>
      <pubDate>Thu, 03 Apr 2025 19:47:43 +0000</pubDate>
      <description><![CDATA[<data class="p-rating" value="5">★★★★★</data>
<span class="u-review-of">
    
        <a class="h-item" href="https://www.rifters.com/real/Blindsight.htm">Blindsight</a>
    
    by
    
        <a href="https://rifters.com/">Peter Watts</a>
    
</span>

<p>Cannot recommend this enough! This is one of the novels I often have to think of and read multiple times.</p>
<p><span class="hashtag"><a class="p-category" href="/tags/scifi/">#scifi</a></span></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/re--east-of-eden--and-book-rating-scales/</guid>
      <title>Re: East of Eden, and book rating scales</title>
      <link>https://blog.libove.org/posts/re--east-of-eden--and-book-rating-scales/</link>
      <pubDate>Thu, 03 Apr 2025 19:33:25 +0000</pubDate>
      <description><![CDATA[Reply to: <a href="https://www.mollywhite.net/micro/entry/east-of-eden">https://www.mollywhite.net/micro/entry/east-of-eden</a>

<p>Absolutely agree. There are many books I rated 5/5 because. I enjoyed them and would definitely recommend them to anyone asking. But then their are books which changed my way of thinking or world perspective. They need their own rating. A &quot;this goes up to eleven!&quot;.</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/falcon/</guid>
      <title>Falcon</title>
      <link>https://blog.libove.org/posts/falcon/</link>
      <pubDate>Sat, 29 Mar 2025 16:30:21 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC00610.jpg" class="u-photo">
    <img src="/thumbnail/DSC00610.jpg" alt="Falcon" />
</a>

<p><span class="hashtag"><a class="p-category" href="/tags/photography/">#photography</a></span> <span class="hashtag"><a class="p-category" href="/tags/bird/">#bird</a></span> <span class="hashtag"><a class="p-category" href="/tags/nature/">#nature</a></span></p>


]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/zaunk-nig/</guid>
      <title>Zaunkönig</title>
      <link>https://blog.libove.org/posts/zaunk-nig/</link>
      <pubDate>Sat, 29 Mar 2025 16:27:10 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC00674.jpg" class="u-photo">
    <img src="/thumbnail/DSC00674.jpg" alt="Zaunkönig" />
</a>

<p><span class="hashtag"><a class="p-category" href="/tags/photography/">#photography</a></span> <span class="hashtag"><a class="p-category" href="/tags/bird/">#bird</a></span> <span class="hashtag"><a class="p-category" href="/tags/nature/">#nature</a></span></p>


]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/lone-suvivor/</guid>
      <title>Lone Suvivor</title>
      <link>https://blog.libove.org/posts/lone-suvivor/</link>
      <pubDate>Sat, 29 Mar 2025 16:26:43 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC00648.jpg" class="u-photo">
    <img src="/thumbnail/DSC00648.jpg" alt="Lone Suvivor" />
</a>

<p><span class="hashtag"><a class="p-category" href="/tags/photography/">#photography</a></span> <span class="hashtag"><a class="p-category" href="/tags/tree/">#tree</a></span></p>


]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/ready-for-lift-off/</guid>
      <title>Ready for Lift Off</title>
      <link>https://blog.libove.org/posts/ready-for-lift-off/</link>
      <pubDate>Thu, 20 Mar 2025 20:41:13 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC00563.jpg" class="u-photo">
    <img src="/thumbnail/DSC00563.jpg" alt="Ready for Lift Off" />
</a>

<p><span class="hashtag"><a class="p-category" href="/tags/bird/">#bird</a></span> <span class="hashtag"><a class="p-category" href="/tags/photography/">#photography</a></span></p>


]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/woodpecker-2/</guid>
      <title>Woodpecker 2</title>
      <link>https://blog.libove.org/posts/woodpecker-2/</link>
      <pubDate>Thu, 20 Mar 2025 20:40:40 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC00573.jpg" class="u-photo">
    <img src="/thumbnail/DSC00573.jpg" alt="Woodpecker 2" />
</a>

<p><span class="hashtag"><a class="p-category" href="/tags/bird/">#bird</a></span> <span class="hashtag"><a class="p-category" href="/tags/photography/">#photography</a></span></p>


]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/on-the-fence/</guid>
      <title>On the Fence</title>
      <link>https://blog.libove.org/posts/on-the-fence/</link>
      <pubDate>Thu, 20 Mar 2025 20:40:21 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC00549.jpg" class="u-photo">
    <img src="/thumbnail/DSC00549.jpg" alt="On the Fence" />
</a>

<p><span class="hashtag"><a class="p-category" href="/tags/bird/">#bird</a></span> <span class="hashtag"><a class="p-category" href="/tags/photography/">#photography</a></span></p>


]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/below-the-bird-feeder/</guid>
      <title>Below the Bird Feeder</title>
      <link>https://blog.libove.org/posts/below-the-bird-feeder/</link>
      <pubDate>Thu, 20 Mar 2025 20:40:05 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC00528.jpg" class="u-photo">
    <img src="/thumbnail/DSC00528.jpg" alt="Below the Bird Feeder" />
</a>

<p><span class="hashtag"><a class="p-category" href="/tags/bird/">#bird</a></span> <span class="hashtag"><a class="p-category" href="/tags/photography/">#photography</a></span></p>


]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/kernbei-er/</guid>
      <title>Kernbeißer</title>
      <link>https://blog.libove.org/posts/kernbei-er/</link>
      <pubDate>Fri, 07 Mar 2025 09:57:10 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC00499.jpg" class="u-photo">
    <img src="/thumbnail/DSC00499.jpg" alt="Kernbeißer" />
</a>

<p><span class="hashtag"><a class="p-category" href="/tags/bird/">#bird</a></span> <span class="hashtag"><a class="p-category" href="/tags/photography/">#photography</a></span> <span class="hashtag"><a class="p-category" href="/tags/nature/">#nature</a></span></p>


]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/woodpecker/</guid>
      <title>Woodpecker</title>
      <link>https://blog.libove.org/posts/woodpecker/</link>
      <pubDate>Fri, 07 Mar 2025 09:50:39 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC00512.jpg" class="u-photo">
    <img src="/thumbnail/DSC00512.jpg" alt="Woodpecker" />
</a>

<p><span class="hashtag"><a class="p-category" href="/tags/bird/">#bird</a></span> <span class="hashtag"><a class="p-category" href="/tags/photography/">#photography</a></span> <span class="hashtag"><a class="p-category" href="/tags/nature/">#nature</a></span></p>


]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/review--accelerando-by-charles-stross/</guid>
      <title>Review: Accelerando by Charles Stross</title>
      <link>https://blog.libove.org/posts/review--accelerando-by-charles-stross/</link>
      <pubDate>Tue, 04 Feb 2025 20:27:46 +0000</pubDate>
      <description><![CDATA[<data class="p-rating" value="5">★★★★★</data>
<span class="u-review-of">
    
        <a class="h-item" href="http://www.antipope.org/charlie/blog-static/fiction/accelerando/accelerando-intro.html">Accelerando</a>
    
    by
    
        <a href="https://www.antipope.org">Charles Stross</a>
    
</span>

<p>One of my favorite science fiction books. It plays in a world shortly before and after the AI singularity. I don't know how to describe it in a review without spoiling the journey, but I found many parallel to technologies and trends which should only emerge years after the publication of Accelerando.</p>
<p><span class="hashtag"><a class="p-category" href="/tags/scifi/">#scifi</a></span> <span class="hashtag"><a class="p-category" href="/tags/ai/">#ai</a></span></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/visualisierung--sonntagsfrage/</guid>
      <title>Visualisierung: Sonntagsfrage</title>
      <link>https://blog.libove.org/posts/visualisierung--sonntagsfrage/</link>
      <pubDate>Wed, 29 Jan 2025 20:20:40 +0000</pubDate>
      <description><![CDATA[<p>Mit der anstehenden Bundestagswahl 2025 habe ich mich mal einen Abend hingesetzt um den Verlauf der Umfrageergebnisse zu visualisieren. Die Daten stammen von <a href="https://wahlrecht.de">https://wahlrecht.de</a>.</p>
<p>Punkte zeigen einzelne Erhebungen, die Linie ist der Durchschnitt der letzten 14 Befragungen. Die Daten aller Institute wurde genutzt. Bei der &quot;Gesellschaft für Markt- und Sozialforschung&quot; habe ich die Sonstigen und BSW verworfen, da diese zusammen in einer Spalte aufgelistet wurden.</p>
<p><a href="/media/btw25.png"><img src="/thumbnail/btw25.png" alt="Visualisierung der Sonntagsfragen seit der letzten Bundestagswahl. Wahl und Bruch der Ampelkoalition sind markiert."></a></p>
<p><span class="hashtag"><a class="p-category" href="/tags/politik/">#politik</a></span> <span class="hashtag"><a class="p-category" href="/tags/dataviz/">#dataviz</a></span> <span class="hashtag"><a class="p-category" href="/tags/btw25/">#btw25</a></span> <span class="hashtag"><a class="p-category" href="/tags/data/">#data</a></span></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/blaumeise/</guid>
      <title>Blaumeise</title>
      <link>https://blog.libove.org/posts/blaumeise/</link>
      <pubDate>Sun, 19 Jan 2025 20:38:25 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC00430.jpg" class="u-photo">
    <img src="/thumbnail/DSC00430.jpg" alt="Blaumeise" />
</a>

<p><span class="hashtag"><a class="p-category" href="/tags/bird/">#bird</a></span> <span class="hashtag"><a class="p-category" href="/tags/photography/">#photography</a></span> <span class="hashtag"><a class="p-category" href="/tags/nature/">#nature</a></span></p>


]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/update--wahlprogramme-tool/</guid>
      <title>Update: Wahlprogramme Tool</title>
      <link>https://blog.libove.org/posts/update--wahlprogramme-tool/</link>
      <pubDate>Tue, 31 Dec 2024 10:39:55 +0000</pubDate>
      <description><![CDATA[<p>Ich hatte zur letzten Bundestagwahl ein kleines Tool gebaut um die <a href="https://wahlprogramme.rerere.org/">Themen in Wahlprogrammen zu durchsuchen und zu visualisieren</a>.</p>
<p>Mit der anstehenden Wahl wurde es Zeit die neuen Programme einzupflegen. Derzeit sind die meisten Programme nur Entwürfe, lediglich die Union hat schon ein fertiges Wahlprogramm.</p>
<p>Ein kleine Anleitung findet ihr hier: <a href="https://blog.libove.org/posts/wahlprogramme/">https://blog.libove.org/posts/wahlprogramme/</a></p>
<p>Der Source Code ist auf GitHub verfügbar: <a href="https://github.com/H4kor/wahlprogramme">https://github.com/H4kor/wahlprogramme</a></p>
<p><a href="/media/2025-1.png"><img src="/thumbnail/2025-1.png" alt="Erwähnungen der Themen &quot;Klima&quot; und &quot;KI&quot; in den Wahlprogrammen 2025"></a></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/veganes-weihnachtsessen-/</guid>
      <title>Veganes Weihnachtsessen </title>
      <link>https://blog.libove.org/posts/veganes-weihnachtsessen-/</link>
      <pubDate>Tue, 24 Dec 2024 19:30:47 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC00068.JPG" class="u-photo">
    <img src="/thumbnail/DSC00068.JPG" alt="Veganes Weihnachtsessen " />
</a>

<p><span class="hashtag"><a class="p-category" href="/tags/vegan/">#vegan</a></span> Rouladen mit Rotkohl und Knödeln</p>


]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/guardian-cat/</guid>
      <title>Guardian Cat</title>
      <link>https://blog.libove.org/posts/guardian-cat/</link>
      <pubDate>Wed, 18 Dec 2024 21:10:19 +0000</pubDate>
      <description><![CDATA[<a href="/media/ded77899b84bc379.jpg" class="u-photo">
    <img src="/thumbnail/ded77899b84bc379.jpg" alt="Guardian Cat" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/-chili--sin-carne/</guid>
      <title>&#34;Chili&#34; Sin Carne</title>
      <link>https://blog.libove.org/posts/-chili--sin-carne/</link>
      <pubDate>Sun, 04 Aug 2024 17:30:24 +0000</pubDate>
      <description><![CDATA[<small>
    
    Servings: <span class="p-yield">1 großer Topf</span>
    , 

    

    
    Prep Time: <time class="dt-duration" value="2 Stunden">
        2 Stunden
    </time>
    
</small>
<br> <br>

<h2>Ingredients</h2>

<ul>
    
    <li class="p-ingredient">
        4 Tofuhack
    </li>
    
    <li class="p-ingredient">
        1 große Zucchini
    </li>
    
    <li class="p-ingredient">
        3 Paprika
    </li>
    
    <li class="p-ingredient">
        4 Zwiebeln
    </li>
    
    <li class="p-ingredient">
        8 Knoblauchzehen
    </li>
    
    <li class="p-ingredient">
        3 Lorbeerblätter
    </li>
    
    <li class="p-ingredient">
        4 Dosen Tomatenstücke
    </li>
    
    <li class="p-ingredient">
        4 TL Paprikapulver
    </li>
    
    <li class="p-ingredient">
        2 TL Kreuzkümmel (gemahlen)
    </li>
    
    <li class="p-ingredient">
        2 TL Oregano
    </li>
    
    <li class="p-ingredient">
        1 TL Zwiebelpulver
    </li>
    
    <li class="p-ingredient">
        1 TL gemahlener Pfeffer
    </li>
    
    <li class="p-ingredient">
        2 EL Tomatenmark
    </li>
    
    <li class="p-ingredient">
        1 Liter Gemüsebrühe
    </li>
    
    <li class="p-ingredient">
        500 g Vollkorn Langkornreis
    </li>
    
    <li class="p-ingredient">
        3 Dosen Kidney Bohnen
    </li>
    
</ul>

<h2>Instructions</h2>
<p>&quot;Chili&quot; sin Carne Rezept für große Mengen zum einfrieren.</p>
<ul>
<li>Zucchini, Paprika und Zwiebeln würfeln</li>
<li>Knoblauch hacken</li>
<li>Gewürze mischen</li>
<li>Tofuhack in Öl anbraten</li>
<li>Gemüse hinzufügen und ein paar Minuten mit anbraten</li>
<li>Gewürze und Tomatenmark kurz mit anbraten</li>
<li>Mit Gemüsebrühe ablöschen und Tomatenstücke hinzufügen</li>
<li>Kidney Bohnen und Reis hinzufügen</li>
<li>~40 Minuten köcheln lassen bis Reis gar ist. Wasser hinzufügen falls nötig</li>
</ul>
<p><span class="hashtag"><a class="p-category" href="/tags/vegan/">#vegan</a></span> <span class="hashtag"><a class="p-category" href="/tags/chili/">#chili</a></span></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/owl-blogs-0-3-2/</guid>
      <title>owl-blogs 0.3.2</title>
      <link>https://blog.libove.org/posts/owl-blogs-0-3-2/</link>
      <pubDate>Sat, 20 Jul 2024 18:43:31 +0000</pubDate>
      <description><![CDATA[<p>I've worked on some smaller features and improvements for <a href="https://github.com/H4kor/owl-blogs/releases/tag/v0.3.2">owl-blogs</a>.</p>
<p>Main Features:</p>
<ul>
<li>Lists and Tags now have RSS Feeds.</li>
<li>Nicer 404 Pages</li>
</ul>
<p>For development I took the time to setup the &quot;end-to-end&quot; tests using go test instead of the previous pytest setup. This vastly simplifies testing and its much quicker.</p>
<p>To test <span class="hashtag"><a class="p-category" href="/tags/activitypub/">#ActivityPub</a></span> functionality I use a <a href="https://github.com/H4kor/owl-blogs/blob/main/tests/mock.go">small mock server</a>, which content can be controlled during testing.</p>
<p><span class="hashtag"><a class="p-category" href="/tags/owlblogs/">#owlblogs</a></span></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/re--indieauth/</guid>
      <title>Re: IndieAuth</title>
      <link>https://blog.libove.org/posts/re--indieauth/</link>
      <pubDate>Sat, 20 Jul 2024 17:08:32 +0000</pubDate>
      <description><![CDATA[Reply to: <a href="https://indieweb.social/@OpenMentions/112773889161182871">https://indieweb.social/@OpenMentions/112773889161182871</a>

<p>I've had <span class="hashtag"><a class="p-category" href="/tags/indieauth/">#IndieAuth</a></span> implemented in the &quot;v1&quot; of my blog, but only used it for the the <span class="hashtag"><a class="p-category" href="/tags/indieweb/">#IndieWeb</a></span> wiki. Did not yet bother to reimplement in v2.</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/rust-gtk--button-with-custom-icon-with-gtk-rs-using-embedded-images/</guid>
      <title>Rust GTK: Button with Custom Icon with gtk-rs using Embedded Images</title>
      <link>https://blog.libove.org/posts/rust-gtk--button-with-custom-icon-with-gtk-rs-using-embedded-images/</link>
      <pubDate>Wed, 17 Jul 2024 18:54:08 +0000</pubDate>
      <description><![CDATA[<p><em><strong>This guide is written fro gtk-rs and GTK 4.6</strong></em></p>
<p>For my <a href="https://h4kor.github.io/dungeon-planner/">DungeonPlanner</a> project I wanted to use custom icons for the tool buttons. Unfortunately the documentation for this is rather slim. As an additional constraint, the image data should be embedded in the binary as I like to compile and ship a single binary file (as long as this is possible).</p>
<p><a href="/media/tools.png"><img src="/thumbnail/tools.png" alt="Resulting tool bar in DungeonPlanner with custom icons"></a></p>
<p>The nearest approach I could find was to create <a href="https://users.rust-lang.org/t/gtk-rs-creating-a-button-with-an-image-downloaded-from-a-url/55024/8">buttons with downloaded images</a>. The snippet is for GTK3 so it had to be adjusted for GTK4, but it contained the right function names to know what to search for :).</p>
<p>This is the final code I ended up with:</p>
<pre><code class="language-rust">let button = Button::new();
let bytes = include_bytes!(&quot;../../assets/icons/add_chamber.png&quot;);
let g_bytes = glib::Bytes::from(&amp;bytes.to_vec());
let stream = MemoryInputStream::from_bytes(&amp;g_bytes);
let pixbuf = Pixbuf::from_stream(&amp;stream, Cancellable::NONE).unwrap();
let texture = Texture::for_pixbuf(&amp;pixbuf);
let image = Image::from_paintable(Some(&amp;texture));
button.set_child(Some(&amp;image));
</code></pre>
<p>We start by creating a button. Instead of using the <a href="https://gtk-rs.org/gtk4-rs/stable/latest/docs/gtk4/builders/struct.ButtonBuilder.html">ButtonBuilder</a> as you would normally do, I'm just creating an &quot;empty&quot; button. It should be possible to still use the builder, as we are just replacing the child content at the end.</p>
<pre><code class="language-rust">let button = Button::new();
</code></pre>
<p>Next we need to load our image data. As I want my images to be embedded in the binary I use the <a href="https://doc.rust-lang.org/std/macro.include_bytes.html">include_bytes!</a> macro. The raw bytes are then turned into a <a href="https://gtk-rs.org/gtk-rs-core/stable/latest/docs/glib/struct.Bytes.html">glib:Bytes</a> struct and finally into a <a href="https://gtk-rs.org/gtk-rs-core/stable/latest/docs/gio/struct.MemoryInputStream.html">MemoryInputStream</a>. The stream is needed to parse the image data.</p>
<pre><code class="language-rust">let bytes = include_bytes!(&quot;../../assets/icons/add_chamber.png&quot;);
let g_bytes = glib::Bytes::from(&amp;bytes.to_vec());
let stream = MemoryInputStream::from_bytes(&amp;g_bytes);
</code></pre>
<p>The next goal is to create an Image object containing our embedded image. With GTK 4.6 we could still use <a href="https://gtk-rs.org/gtk4-rs/stable/latest/docs/gtk4/struct.Image.html#method.from_pixbuf">Image::from_pixbuf</a> but this will be deprecated in GTK 4.12. Instead we have to do an extra step and create a <a href="https://gtk-rs.org/gtk4-rs/stable/latest/docs/gdk4/struct.Texture.html#">Texture</a> and use <a href="https://gtk-rs.org/gtk4-rs/stable/latest/docs/gtk4/struct.Image.html#method.from_paintable">Image::from_paintable</a>. The texture can simply be created from a <a href="https://gtk-rs.org/gtk-rs-core/stable/0.20/docs/gdk_pixbuf/struct.Pixbuf.html">Pixbuf</a>, which is created by using the <a href="https://gtk-rs.org/gtk-rs-core/stable/0.20/docs/gdk_pixbuf/struct.Pixbuf.html#method.from_stream">Pixbuf::from_stream</a> function</p>
<pre><code class="language-rust">let pixbuf = Pixbuf::from_stream(&amp;stream, Cancellable::NONE).unwrap();
let texture = Texture::for_pixbuf(&amp;pixbuf);
let image = Image::from_paintable(Some(&amp;texture));
</code></pre>
<p>Finally we can set the child of our button to the image and our icon button is done. The same approach also works for ToggleButton.</p>
<pre><code class="language-rust">button.set_child(Some(&amp;image));
</code></pre>
<p><span class="hashtag"><a class="p-category" href="/tags/gtk/">#gtk</a></span> <span class="hashtag"><a class="p-category" href="/tags/gtk4/">#gtk4</a></span> <span class="hashtag"><a class="p-category" href="/tags/rust/">#rust</a></span> <span class="hashtag"><a class="p-category" href="/tags/gtkrs/">#gtkrs</a></span></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/missed-shot-at-artificial-general-intelligence/</guid>
      <title>Missed Shot at Artificial General Intelligence</title>
      <link>https://blog.libove.org/posts/missed-shot-at-artificial-general-intelligence/</link>
      <pubDate>Wed, 10 Jul 2024 20:12:17 +0000</pubDate>
      <description><![CDATA[<p>The stated <a href="https://openai.com/about/">goal of OpenAI</a> is <em>&quot;to ensure that artificial general intelligence benefits all of humanity&quot;</em>.
With the release of ChatGPT, they might have missed humanity’s only shot at creating an Artificial General Intelligence (AGI).</p>
<h2>It's all about data</h2>
<p>The performance (&quot;intelligence&quot;) of large language models (LLMs) mainly depends on the scale of its training data and the size of the model [<a href="https://arxiv.org/abs/2001.08361">1</a>].
To create a better LLM you need to increase its size, and train it on more data.
The architecture and configuration of an LLM can almost be neglected in comparison.</p>
<p>However, the intelligence of a model does not grow linearly with its size [<a href="https://arxiv.org/abs/2001.08361">1</a>].
There is a diminishing return on increasing the size of LLMs.
If you increase the size of model and training set by 10x, you will only get an increase in performance as the previous 10x increase accomplished.
This explains why vast amounts of data are required to effectively train large language model.</p>
<p>ChatGPT 3 was trained on 500 billion tokens [<a href="https://arxiv.org/abs/2005.14165">2</a>].
There are no official numbers (that I could find) on how much data was used to train ChatGPT 4, but rumors in the AI community state that it was <a href="https://the-decoder.com/gpt-4-architecture-datasets-costs-and-more-leaked/">trained on 13 trillion tokens</a>.
With this numbers the performance step from 3 to 4 took an 26x increase in data.</p>
<p>The estimation for the size of publicly available, de duplicated data is 320 trillion tokens [<a href="https://arxiv.org/abs/2211.04325">4</a>].
This is 24.6x more data than ChatGPT4 was (likely) trained on.
If these numbers are correct, the performance of LLMs will only increase as much as it increased from ChatGPT3 to ChatGPT4 before we ran out of data.</p>
<p>I doubt that this will be enough to reach AGI level intelligence.</p>
<h2>Poisoned Data</h2>
<p>Now you might say &quot;we produce more data every day, models can get better in the future&quot;.
We could just wait some decades, train new models from time to time and see a gradual increase in performance.
And one day we suddenly have an AGI.
But the release of ChatGPT created a problem.
It poisoned any data collected after <a href="https://openai.com/index/chatgpt/">November 30, 2022</a>.</p>
<p>The release of ChatGPT was the wet dream of every spammer, bot operator, troll and wannabe influencer.
Suddenly you could create seemingly high quality <em>content</em>, indistinguishable from human written text, at virtually zero cost.
Ever since the internet gets filled with LLM generated <em>content</em>.
And this is a problem for all future trainings.</p>
<p>Models that are trained on their own generations (or data created by other models) start to forget, there performance declines [<a href="https://arxiv.org/abs/2305.17493">3</a>].
Therefore you have to avoid training on AI generated <em>content</em>, otherwise the increase in data may decrease your performance.
As it is virtually impossible to clean a dataset from AI generated text, any data collected after Nov. 2022 should be avoided.
Maybe you can still use a few more months or years of data, but at some point more data will hurt the model more than it helps.</p>
<p>The publicly available data that we got now is all we will get to train an AGI.
If we need more data it will have to collected at an exorbitant price to ensure it's not poisoned by AI generated data.</p>
<h2>We reached a dead end</h2>
<p>The size of current training sets and potentially available data shows that we will not reach AGI levels with the current state of the art approaches.
Due to data poisoning we will not get substantially more usable data.
Thereby AGI is (currently) not achievable.
Opening pandora's box of accessible generative AI may killed our chance of creating an artificial general intelligence.</p>
<p><em>If</em> we want to build an AGI we will have to do it with the data we have <strong>now</strong>.</p>
<p><span class="hashtag"><a class="p-category" href="/tags/ai/">#AI</a></span> <span class="hashtag"><a class="p-category" href="/tags/agi/">#AGI</a></span> <span class="hashtag"><a class="p-category" href="/tags/llm/">#LLM</a></span> <span class="hashtag"><a class="p-category" href="/tags/genai/">#GenAI</a></span></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/szechuan-pfeffer-udon-nudeln/</guid>
      <title>Szechuan Pfeffer Udon Nudeln</title>
      <link>https://blog.libove.org/posts/szechuan-pfeffer-udon-nudeln/</link>
      <pubDate>Sun, 30 Jun 2024 09:19:37 +0000</pubDate>
      <description><![CDATA[<small>
    
    Servings: <span class="p-yield">1 Portion</span>
    , 

    

    
    Prep Time: <time class="dt-duration" value="20 Minuten">
        20 Minuten
    </time>
    
</small>
<br> <br>

<h2>Ingredients</h2>

<ul>
    
    <li class="p-ingredient">
        100 g Udon Nudeln
    </li>
    
    <li class="p-ingredient">
        Handvoll Sojaschnetzel
    </li>
    
    <li class="p-ingredient">
        1 kleine Zucchini
    </li>
    
    <li class="p-ingredient">
        1 kleine Karotte
    </li>
    
    <li class="p-ingredient">
        1 EL Szechuan Pfeffer
    </li>
    
    <li class="p-ingredient">
        1 Knoblauchzehe
    </li>
    
    <li class="p-ingredient">
        1 cm Ingwer
    </li>
    
    <li class="p-ingredient">
        1 EL schwarze Bohnenpaste
    </li>
    
    <li class="p-ingredient">
        1 EL Sojasauce
    </li>
    
    <li class="p-ingredient">
        1 EL Agavendicksaft
    </li>
    
    <li class="p-ingredient">
        200 ml Wasser
    </li>
    
</ul>

<h2>Instructions</h2>
<p>Dieses Rezept war ein bisschen frei Schnauze, ist aber wirklich gut geworden. Die Mengen sind im nachhinein geschätzt, also abschmecken und gegebenfalls anpassen.</p>
<p><a href="/media/szechuan-pfeffer-nudeln.jpeg"><img src="/thumbnail/szechuan-pfeffer-nudeln.jpeg" alt="Szechuan Pfeffer Udon Nudeln"></a></p>
<ul>
<li>Sojaschnetzeln kochen</li>
<li>Knoblauch und Ingwer fein hacken</li>
<li>Alle Gewürze und Wasser mischen</li>
<li>Karotten und Zucchini in dünne Scheiben schneiden</li>
<li>Nudeln nach Packung kochen</li>
<li>Sojaschnetzel anbraten</li>
<li>Zucchini und Karotten für 2 Minuten mitanbraten</li>
<li>Mit Gewürzmischung ablöschen</li>
<li>Sauce reduzieren und Nudeln unterheben</li>
</ul>
<p><span class="hashtag"><a class="p-category" href="/tags/vegan/">#vegan</a></span> <span class="hashtag"><a class="p-category" href="/tags/nudeln/">#nudeln</a></span> <span class="hashtag"><a class="p-category" href="/tags/udon/">#udon</a></span> <span class="hashtag"><a class="p-category" href="/tags/zucchini/">#zucchini</a></span> <span class="hashtag"><a class="p-category" href="/tags/karotten/">#karotten</a></span> <span class="hashtag"><a class="p-category" href="/tags/szechuan/">#szechuan</a></span> <span class="hashtag"><a class="p-category" href="/tags/szechuanpfeffer/">#szechuanpfeffer</a></span></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/random-tables-for-dnd-5e/</guid>
      <title>Random Tables for DnD 5e</title>
      <link>https://blog.libove.org/posts/random-tables-for-dnd-5e/</link>
      <pubDate>Sat, 29 Jun 2024 15:05:38 +0000</pubDate>
      <description><![CDATA[<p>List of random tables I've created to quickly create shops or treasures when DMing.</p>
<h1>Spells</h1>
<ul>
<li><a href="https://tools.libove.org/generators/table/5e-level-0-spells/">Level 0 Spells</a></li>
<li><a href="https://tools.libove.org/generators/table/5e-level-1-spells/">Level 1 Spells</a></li>
<li><a href="https://tools.libove.org/generators/table/5e-level-2-spells/">Level 2 Spells</a></li>
<li><a href="https://tools.libove.org/generators/table/5e-level-3-spells/">Level 3 Spells</a></li>
<li><a href="https://tools.libove.org/generators/table/5e-level-4-spells/">Level 4 Spells</a></li>
<li><a href="https://tools.libove.org/generators/table/5e-level-5-spells/">Level 5 Spells</a></li>
<li><a href="https://tools.libove.org/generators/table/5e-level-6-spells/">Level 6 Spells</a></li>
<li><a href="https://tools.libove.org/generators/table/5e-level-7-spells/">Level 7 Spells</a></li>
<li><a href="https://tools.libove.org/generators/table/5e-level-8-spells/">Level 8 Spells</a></li>
<li><a href="https://tools.libove.org/generators/table/5e-level-9-spells/">Level 9 Spells</a></li>
</ul>
<h1>Magic Items</h1>
<h2>Armor</h2>
<ul>
<li><a href="https://tools.libove.org/generators/table/5e-uncommon-magic-armor/">Uncommon Armor</a></li>
<li><a href="https://tools.libove.org/generators/table/5e-rare-magic-armor/">Rare Armor</a></li>
<li><a href="https://tools.libove.org/generators/table/5-very-rage-magic-armor/">Very Rare Armor</a></li>
<li><a href="https://tools.libove.org/generators/table/5e-legendary-magic-armor/">Legendary Armor</a></li>
</ul>
<h2>Rings</h2>
<ul>
<li><a href="https://tools.libove.org/generators/table/5e-uncommon-ring/">Uncommon Ring</a></li>
<li><a href="https://tools.libove.org/generators/table/5e-rare-ring/">Rare Ring</a></li>
<li><a href="https://tools.libove.org/generators/table/5e-very-rare-ring/">Very Rare Ring</a></li>
<li><a href="https://tools.libove.org/generators/table/5e-legendary-ring/">Legendary Ring</a></li>
</ul>
<h2>Rods</h2>
<ul>
<li><a href="https://tools.libove.org/generators/table/5e-uncommon-rod/">Uncommon Rod</a></li>
<li><a href="https://tools.libove.org/generators/table/5e-rare-rod/">Rare Rod</a></li>
<li><a href="https://tools.libove.org/generators/table/5e-very-rare-rod/">Very Rare Rod</a></li>
<li><a href="https://tools.libove.org/generators/table/5e-legendary-rod/">Legendary Rod</a></li>
</ul>
<h2>Staves</h2>
<ul>
<li><a href="https://tools.libove.org/generators/table/5e-uncommon-staff/">Uncommon Staff</a></li>
<li><a href="https://tools.libove.org/generators/table/5e-rare-staff/">Rare Staff</a></li>
<li><a href="https://tools.libove.org/generators/table/5e-very-rare-staff/">Very Rare Staff</a></li>
<li>Only one Legendary Staff: Staff of the Magi</li>
</ul>
<h2>Weapons</h2>
<ul>
<li><a href="https://tools.libove.org/generators/table/5e-uncommon-magic-weapon/">Uncommon Magic Weapon</a></li>
<li><a href="https://tools.libove.org/generators/table/5e-rare-magic-weapon/">Rare Magic Weapon</a></li>
<li><a href="https://tools.libove.org/generators/table/5e-very-rare-magic-weapon/">Very Rare Magic Weapon</a></li>
<li><a href="https://tools.libove.org/generators/table/5e-legendary-magic-weapon/">Legendary Magic Weapon</a></li>
</ul>
<h2>Wondrous Items</h2>
<ul>
<li><a href="https://tools.libove.org/generators/table/5e-uncommon-wondrous-item/">Uncommon Wondrous Item</a></li>
<li><a href="https://tools.libove.org/generators/table/5e-rare-wondrous-item/">Rare Wondrous Item</a></li>
<li><a href="https://tools.libove.org/generators/table/5e-very-rare-wondrous-item/">Very Rare Wondrous Item</a></li>
<li><a href="https://tools.libove.org/generators/table/5e-legendary-wondrous-item/">Legendary Wondrous Item</a></li>
</ul>
<h1>Generators</h1>
<ul>
<li><a href="https://tools.libove.org/generators/texts/5e-random-potion-shop/">Random Potion Shop</a></li>
<li><a href="https://tools.libove.org/generators/texts/5e-random-scroll-shop/">Random Scrolls Shop</a></li>
</ul>
<h1>Gamemaster Guide Tables</h1>
<ul>
<li><a href="https://tools.libove.org/generators/table/5e-magic-item-table-g/">Magic Item Table G</a></li>
</ul>
<p><span class="hashtag"><a class="p-category" href="/tags/dnd/">#DnD</a></span> #5e <span class="hashtag"><a class="p-category" href="/tags/random/">#random</a></span> <span class="hashtag"><a class="p-category" href="/tags/generator/">#generator</a></span> <span class="hashtag"><a class="p-category" href="/tags/tabletop/">#tabletop</a></span></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/nudeln-mi-erbsen/</guid>
      <title>Nudeln mit Erbsen</title>
      <link>https://blog.libove.org/posts/nudeln-mi-erbsen/</link>
      <pubDate>Sun, 23 Jun 2024 10:01:53 +0000</pubDate>
      <description><![CDATA[<small>
    
    Servings: <span class="p-yield">3 Portionen</span>
    , 

    

    
    Prep Time: <time class="dt-duration" value="20 Minuten">
        20 Minuten
    </time>
    
</small>
<br> <br>

<h2>Ingredients</h2>

<ul>
    
    <li class="p-ingredient">
        250g Nudeln
    </li>
    
    <li class="p-ingredient">
        200g TK Erbsen
    </li>
    
    <li class="p-ingredient">
        200ml vegane Sahne
    </li>
    
    <li class="p-ingredient">
        1 Zwiebel
    </li>
    
    <li class="p-ingredient">
        1 Knoblauchzehe
    </li>
    
    <li class="p-ingredient">
        50g Hefeflocken
    </li>
    
    <li class="p-ingredient">
        1 EL Olivenöl
    </li>
    
    <li class="p-ingredient">
        1 EL Tomatenmark
    </li>
    
    <li class="p-ingredient">
        1 TL Paprikapulver
    </li>
    
    <li class="p-ingredient">
        1 TL Oregano
    </li>
    
    <li class="p-ingredient">
        Salz und Pfeffer
    </li>
    
</ul>

<h2>Instructions</h2>
<p>Ein einfaches und schnelles Nudelgericht ideal für Kleinkinder. Schmeckt gut mit <a href="https://blog.libove.org/posts/veganer-parmesan-/">veganem Parmesan</a>.</p>
<ul>
<li>Zwiebel würfeln und Knoblauch fein hacken</li>
<li>Nudeln kochen</li>
<li>Zwiebeln und Knoblauch in Olivenöl glasig anbraten</li>
<li>Tomatenmark für 2-3 Minuten mitanbraten</li>
<li>Erbsen, Hefeflocken und Gewürze hinzugeben, kurz mit anbraten</li>
<li>Sahne und 100ml Wasser hinzugeben und gut verrühren</li>
<li>Sauce unter gelegentlichem Rühren aufkochen</li>
<li>Nudeln abgießen und alles gut durchmischen</li>
<li>Mit Salz und Pfeffer abschmecken</li>
</ul>
<p><span class="hashtag"><a class="p-category" href="/tags/vegan/">#vegan</a></span> <span class="hashtag"><a class="p-category" href="/tags/rezept/">#rezept</a></span> <span class="hashtag"><a class="p-category" href="/tags/nudeln/">#nudeln</a></span> <span class="hashtag"><a class="p-category" href="/tags/erbsen/">#erbsen</a></span></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/veganer-pesto-nudelsalat-mit-rucola/</guid>
      <title>Veganer Pesto Nudelsalat mit Rucola</title>
      <link>https://blog.libove.org/posts/veganer-pesto-nudelsalat-mit-rucola/</link>
      <pubDate>Thu, 20 Jun 2024 19:55:51 +0000</pubDate>
      <description><![CDATA[<small>
    
    Servings: <span class="p-yield">4 Portionen</span>
    , 

    

    
    Prep Time: <time class="dt-duration" value="30 Minuten">
        30 Minuten
    </time>
    
</small>
<br> <br>

<h2>Ingredients</h2>

<ul>
    
    <li class="p-ingredient">
        500g Fusilli Nudeln
    </li>
    
    <li class="p-ingredient">
        250g Cherry Tomaten
    </li>
    
    <li class="p-ingredient">
        125g Rucola
    </li>
    
    <li class="p-ingredient">
        1 Glas veganes grünes Pesto
    </li>
    
    <li class="p-ingredient">
        2 EL Agavendicksaft
    </li>
    
    <li class="p-ingredient">
        2 EL Senf, mittelscharf
    </li>
    
    <li class="p-ingredient">
        50g veganer Parmesan (optional)
    </li>
    
</ul>

<h2>Instructions</h2>
<p><a href="/media/header.jpg"><img src="/thumbnail/header.jpg" alt=""></a></p>
<p>Die vegane Version meines <a href="https://blog.libove.org/posts/2016-09-19-pasta-arugula-salad/">Lieblings-Nudelsalats</a>.</p>
<ul>
<li>Nudeln kochen und abkühlen lassen oder mit kalten Wasser abkühlen</li>
<li>Rucola waschen und abtropfen lassen</li>
<li>Tomaten halbieren oder vierteln</li>
<li>Nudeln, Tomaten, Pesto, Agavendicksaft und Senf vermischen</li>
<li>Mit <a href="https://blog.libove.org/posts/veganer-parmesan-/">veganem Parmesan</a> abschmecken (optional)</li>
<li>Rucola untermischen</li>
</ul>
<p><span class="hashtag"><a class="p-category" href="/tags/vegan/">#vegan</a></span> <span class="hashtag"><a class="p-category" href="/tags/rezept/">#rezept</a></span> <span class="hashtag"><a class="p-category" href="/tags/nudelsalat/">#nudelsalat</a></span> <span class="hashtag"><a class="p-category" href="/tags/einfach/">#einfach</a></span></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/microformat-categories-for-hashtags-in-owl-blogs/</guid>
      <title>Microformat Categories for Hashtags in owl-blogs</title>
      <link>https://blog.libove.org/posts/microformat-categories-for-hashtags-in-owl-blogs/</link>
      <pubDate>Wed, 19 Jun 2024 20:15:25 +0000</pubDate>
      <description><![CDATA[<p>I recently added <a href="https://github.com/H4kor/owl-blogs/commit/1f9e75a702126e488d432999ecf1147b2c5b9165">hashtag support</a> to <a href="https://github.com/H4kor/owl-blogs/">owl-blogs</a>. The initial reason for this was to to make post more discoverable via ActivityPub, but I found it helpful to further categories posts.
The implementation was quiet simple. I use the markdown renderer <a href="https://github.com/yuin/goldmark">goldmark</a> which has a <a href="https://github.com/abhinav/goldmark-hashtag">plugin for hashtags</a>.</p>
<p>As <a href="https://indieweb.org/tags">tags</a> are also part of <a href="http://microformats.org/wiki/p-category">microformats2</a>, I wanted to mark the hashtags accordingly.
This is currently not possible with the hashtag extension.</p>
<p>I've extended this to allow adding arbitrary attributes to the link tag (<a href="https://github.com/abhinav/goldmark-hashtag/pull/46">Related Pull Request</a>).
Until this is merged into the main repository I'll use my own version, which can be done by adding a <a href="https://go.dev/ref/mod#go-mod-file-replace">replace directive</a> to the <code>go.mod</code></p>
<pre><code>replace go.abhg.dev/goldmark/hashtag =&gt; github.com/H4kor/goldmark-hashtag v0.0.0-20240619193802-bec327f5be38
</code></pre>
<p><span class="hashtag"><a class="p-category" href="/tags/go/">#go</a></span> <span class="hashtag"><a class="p-category" href="/tags/dev/">#dev</a></span> <span class="hashtag"><a class="p-category" href="/tags/owlblogs/">#owlblogs</a></span> <span class="hashtag"><a class="p-category" href="/tags/markdown/">#markdown</a></span> <span class="hashtag"><a class="p-category" href="/tags/indieweb/">#indieweb</a></span></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/turtle/</guid>
      <title>Turtle</title>
      <link>https://blog.libove.org/posts/turtle/</link>
      <pubDate>Tue, 18 Jun 2024 19:51:33 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC09787.jpg" class="u-photo">
    <img src="/thumbnail/DSC09787.jpg" alt="Turtle" />
</a>

<p><span class="hashtag"><a class="p-category" href="/tags/emsflower/">#emsflower</a></span> <span class="hashtag"><a class="p-category" href="/tags/turtle/">#turtle</a></span> <span class="hashtag"><a class="p-category" href="/tags/photography/">#photography</a></span></p>


]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/re--commenting-via-mastodon-/</guid>
      <title>Re: Commenting via Mastodon </title>
      <link>https://blog.libove.org/posts/re--commenting-via-mastodon-/</link>
      <pubDate>Sun, 16 Jun 2024 06:18:06 +0000</pubDate>
      <description><![CDATA[Reply to: <a href="https://floss.social/@carlschwan/109774012599031406">https://floss.social/@carlschwan/109774012599031406</a>

<p>A nice and simple solution to add comments to your blog.</p>
<p>I've decided to go the bit more complicated route and added <span class="hashtag"><a class="p-category" href="/tags/activitypub/">#ActivityPub</a></span> support to my blog directly. Any interaction will show up below the posts. However this requires a backend and will not work with a static site generator.</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/thumbnails-for-owl-blogs/</guid>
      <title>Thumbnails for owl-blogs</title>
      <link>https://blog.libove.org/posts/thumbnails-for-owl-blogs/</link>
      <pubDate>Mon, 10 Jun 2024 19:14:50 +0000</pubDate>
      <description><![CDATA[<p>I'm currently working on the <a href="https://github.com/H4kor/owl-blogs/pull/10">thumbnail implementation</a> for <span class="hashtag"><a class="p-category" href="/tags/owl-blogs/">#owl-blogs</a></span> and deployed the feature branch to my blog.</p>
<p>Thumbnails use the same file format as their parent files assuming the user already chose the best format for their images. The thumbnails are created with a width of <code>620px</code>, equal to the content width of the blogs main body. If the image is already small enough the image data is simply copied.</p>
<p>The URL of a thumbnail can simply be generated by replacing <code>/media/</code> with <code>/thumbnail/</code>.</p>
<p>I will still write some tests and see if any errors occur on my blog before merging this feature into the main branch.</p>
<p><span class="hashtag"><a class="p-category" href="/tags/dev/">#dev</a></span> <span class="hashtag"><a class="p-category" href="/tags/blog/">#blog</a></span></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/serving-binary-files-from-sqlite/</guid>
      <title>Serving Binary Files from SQLite</title>
      <link>https://blog.libove.org/posts/serving-binary-files-from-sqlite/</link>
      <pubDate>Thu, 06 Jun 2024 19:56:22 +0000</pubDate>
      <description><![CDATA[<p>My blog software (<a href="https://github.com/H4kor/owl-blogs">owl-blogs</a>) uses a single <a href="https://sqlite.org/">SQLite</a> database to store everything, including all files uploaded. I'm aware that storing large files in a relational database isn't best practice. It started out as a placeholder implementation, but I liked the idea to have a single file I can backup.</p>
<p>One reason against storing binary blobs in relational databases often stated is read performance, but I didn't find any benchmarks supporting this claim. Therefore I built a small test setup to see the difference between serving binary files out of a SQLite database vs serving from the file system directly.</p>
<p>As my blog is written in <a href="https://go.dev/">Go</a>, I created the a simple server similar to my blog. It uses <a href="https://github.com/jmoiron/sqlx">sqlx</a> and <a href="https://github.com/mattn/go-sqlite3">go-sqlite3</a> for the database handling and <code>net/http</code> for the static file server</p>
<pre><code>package main

import (
	&quot;log&quot;
	&quot;net/http&quot;

	&quot;github.com/jmoiron/sqlx&quot;
	_ &quot;github.com/mattn/go-sqlite3&quot;
)

type sqlBinaryFile struct {
	Data []byte `db:&quot;data&quot;`
}

type sqlHandler struct {
	Db *sqlx.DB
}

func (h *sqlHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	id := r.PathValue(&quot;filename&quot;)
	var sqlFile sqlBinaryFile
	h.Db.Get(&amp;sqlFile, &quot;SELECT data FROM files WHERE id = ?&quot;, id)
	w.Write(sqlFile.Data)
}

func main() {

	db := sqlx.MustOpen(&quot;sqlite3&quot;, &quot;files.db&quot;)
	sql := &amp;sqlHandler{Db: db}

	fs := http.StripPrefix(&quot;/dir/&quot;, http.FileServer(http.Dir(&quot;./static&quot;)))
	http.Handle(&quot;/dir/&quot;, fs)
	http.Handle(&quot;/sqlite/{filename}&quot;, sql)

	log.Print(&quot;Listening on :3000...&quot;)
	err := http.ListenAndServe(&quot;:3000&quot;, nil)
	if err != nil {
		log.Fatal(err)
	}
}
</code></pre>
<p>As a test set I created 2000 files between 200kb and 4MB in size using a simple python script:</p>
<pre><code>import os
import random

for i in range(2000):
    os.system(f&quot;head -c {(random.randint(200, 4000))}K &lt;/dev/urandom &gt; static/{i:05d}.bin&quot;)
</code></pre>
<p>The SQLite database was created with this script:</p>
<pre><code>import os
import sqlite3

os.remove(&quot;files.db&quot;)
con = sqlite3.connect(&quot;files.db&quot;)
cur = con.cursor()

cur.execute(&quot;CREATE TABLE files( id VARCHAR(255) PRIMARY KEY, data BLOB NOT NULL )&quot;)

for f in os.listdir(&quot;static&quot;):
    print(f)
    data = open(&quot;static/&quot; + f, &quot;rb&quot;).read()
    cur.execute(&quot;INSERT INTO files(id, data) VALUES (?, ?)&quot;, (f, data))

con.commit()
</code></pre>
<p>To benchmark the server I created two files listing all file URLs (one for sqlite, one fot filesystem) and used <a href="https://github.com/JoeDog/siege">siege</a> to run the benchmark with this configuration.</p>
<pre><code>siege -f urls_sqlite.txt -c 1 -b --time=10s -j
</code></pre>
<p>The test was executed on my laptop:</p>
<ul>
<li>CPU: Intel(R) Core(TM) i7-8565U CPU @ 1.80GHz</li>
<li>CPU max MHz:         4600,0000</li>
<li>Memory: 16 GB</li>
</ul>
<p>I ran the test with different concurrency and plotted the results:</p>
<p><a href="/media/fs_vs_sqlite-1.png"><img src="/thumbnail/fs_vs_sqlite-1.png" alt="Two plots comparing transactions and response time between SQLite and filesystem"></a></p>
<p>For a low throughput system (such as my blog) the difference between SQLite and the filesystem is small enough to not care about.
The possible throughput (transaction/second) of the filesystem is ~2.3 times higher.
The response time grows slower with increased concurrency.</p>
<p>For the time being I will stick with my SQLite solution.
Once my blog gets really popular I can easily change the implementation of the binary repository.</p>
<p><span class="hashtag"><a class="p-category" href="/tags/hosting/">#hosting</a></span> <span class="hashtag"><a class="p-category" href="/tags/sqlite/">#sqlite</a></span> <span class="hashtag"><a class="p-category" href="/tags/go/">#go</a></span> <span class="hashtag"><a class="p-category" href="/tags/benchmark/">#benchmark</a></span> <span class="hashtag"><a class="p-category" href="/tags/server/">#server</a></span></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/re--idea-for-blogpost-/</guid>
      <title>Re: Idea for Blogpost </title>
      <link>https://blog.libove.org/posts/re--idea-for-blogpost-/</link>
      <pubDate>Sat, 01 Jun 2024 16:32:09 +0000</pubDate>
      <description><![CDATA[Reply to: <a href="https://chaos.social/@isotopp/112517363217825291">https://chaos.social/@isotopp/112517363217825291</a>

<p>How many lightbulbs can I control with a single CPU?</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/punk/</guid>
      <title>Punk</title>
      <link>https://blog.libove.org/posts/punk/</link>
      <pubDate>Fri, 31 May 2024 18:29:29 +0000</pubDate>
      <description><![CDATA[<a href="/media/punk.jpg" class="u-photo">
    <img src="/thumbnail/punk.jpg" alt="Punk" />
</a>

<p>Black and white profile picture of an alpaca. Its hair partially covers its eyes.</p>
<p><span class="hashtag"><a class="p-category" href="/tags/photography/">#photography</a></span></p>


]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/re--/</guid>
      <title>Re: Nanosearch</title>
      <link>https://blog.libove.org/posts/re--/</link>
      <pubDate>Fri, 31 May 2024 17:58:52 +0000</pubDate>
      <description><![CDATA[Reply to: <a href="https://jamesg.blog/2024/05/29/nanosearch/">https://jamesg.blog/2024/05/29/nanosearch/</a>

<p>This looks great!
I've been playing with the idea of creating a search engine for a while now.
Maybe this will help to overcome my procrastination :D</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/owl-blogs-goes-activitypub/</guid>
      <title>Owl-blogs goes ActivityPub</title>
      <link>https://blog.libove.org/posts/owl-blogs-goes-activitypub/</link>
      <pubDate>Fri, 31 May 2024 17:38:42 +0000</pubDate>
      <description><![CDATA[<p>I've implemented a basic ActivityPub support into <a href="https://github.com/H4kor/owl-blogs">owl-blogs</a>.</p>
<ul>
<li>Possibility to follow/subscribe to all published entries ActivityPub (e.g. on Mastodon)</li>
<li>Likes, announces (aka boosts) and replies are tracked on posts (<a href="https://blog.libove.org/posts/moved-owl-blogs-to-github/">example post with interactions</a>)</li>
<li>Replies to websites supporting ActivityPub will mention the author.</li>
<li>Support for <a href="https://docs.joinmastodon.org/spec/activitypub/#Hashtag">hashtags as implemented by Mastodon</a></li>
</ul>
<p>Feel free to check out <a href="https://github.com/H4kor/owl-blogs/releases/tag/v0.2.0">version 0.2.0</a>. Feedback is always appreciated :)</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/bird/</guid>
      <title>Bird</title>
      <link>https://blog.libove.org/posts/bird/</link>
      <pubDate>Thu, 30 May 2024 19:34:27 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC09896.jpg" class="u-photo">
    <img src="/thumbnail/DSC09896.jpg" alt="Bird" />
</a>

<p>Shot at <span class="hashtag"><a class="p-category" href="/tags/emsflower/">#Emsflower</a></span>.</p>
<p><span class="hashtag"><a class="p-category" href="/tags/bird/">#Bird</a></span> <span class="hashtag"><a class="p-category" href="/tags/colorful/">#Colorful</a></span> <span class="hashtag"><a class="p-category" href="/tags/photography/">#Photography</a></span></p>


]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/blue-buttefly/</guid>
      <title>Blue Buttefly</title>
      <link>https://blog.libove.org/posts/blue-buttefly/</link>
      <pubDate>Wed, 29 May 2024 19:21:24 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC09545.jpg" class="u-photo">
    <img src="/thumbnail/DSC09545.jpg" alt="Blue Buttefly" />
</a>

<p>Shot at <span class="hashtag"><a class="p-category" href="/tags/emsflower/">#Emsflower</a></span></p>


]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/caterpillar/</guid>
      <title>Caterpillar</title>
      <link>https://blog.libove.org/posts/caterpillar/</link>
      <pubDate>Wed, 29 May 2024 19:20:18 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC09709.jpg" class="u-photo">
    <img src="/thumbnail/DSC09709.jpg" alt="Caterpillar" />
</a>

<p>Shot during day trip to <span class="hashtag"><a class="p-category" href="/tags/emsflower/">#Emsflower</a></span></p>


]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/artificial-flowers/</guid>
      <title>Artificial Flowers</title>
      <link>https://blog.libove.org/posts/artificial-flowers/</link>
      <pubDate>Wed, 29 May 2024 19:15:17 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC09847.jpg" class="u-photo">
    <img src="/thumbnail/DSC09847.jpg" alt="Artificial Flowers" />
</a>

<p>Shot during day trip to <span class="hashtag"><a class="p-category" href="/tags/emsflower/">#Emsflower</a></span></p>


]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/erdnuss-brokkoli-pfanne/</guid>
      <title>Vegane Erdnuss Brokkoli Pfanne</title>
      <link>https://blog.libove.org/posts/erdnuss-brokkoli-pfanne/</link>
      <pubDate>Sun, 19 May 2024 19:59:41 +0000</pubDate>
      <description><![CDATA[<small>
    
    Servings: <span class="p-yield">4 Portionen</span>
    , 

    

    
    Prep Time: <time class="dt-duration" value="30-40 Minuten">
        30-40 Minuten
    </time>
    
</small>
<br> <br>

<h2>Ingredients</h2>

<ul>
    
    <li class="p-ingredient">
        1 Brokkoli
    </li>
    
    <li class="p-ingredient">
        1 handvoll Sojaschnetzel
    </li>
    
    <li class="p-ingredient">
        1 EL Erdnussmus
    </li>
    
    <li class="p-ingredient">
        1 EL Erdnussöl (oder anderes Öl)
    </li>
    
    <li class="p-ingredient">
        6 EL Sojasauce
    </li>
    
    <li class="p-ingredient">
        1 EL Ajvar
    </li>
    
    <li class="p-ingredient">
        1 EL Agavendicksaft
    </li>
    
    <li class="p-ingredient">
        2 Zehen Knoblauch
    </li>
    
    <li class="p-ingredient">
        1 Daumen breit Ingwer
    </li>
    
    <li class="p-ingredient">
        Reis oder Udon Nudeln
    </li>
    
</ul>

<h2>Instructions</h2>
<p>Eine einfache, vegane und nicht scharfe Erdnusssauce. Ich habe das klassische Sambal Oelek (oder Chilisauce) mit Ajvar ausgetauscht, um das Gericht für Kinder anzupassen.</p>
<p>Ich benutze die <a href="https://www.velivery.com/de/vegane-lebensmittel/fleisch-wurst-und-fisch-alternativen/fleisch-alternativen/texturate-und-trockenfleisch-alternativen/vantastic-soy-strips-1kg">Sojaschnetzel von Vantastic</a> als Fleischersatz.
Alternativ kann auch fester Tofu gepresst und in Stärke gewendet benutzt werden.
Oder einfach ganz weglassen, funktioniert trotzdem :).</p>
<p>Serviert wird das Gericht entweder mit Reis oder Udon Nudeln.</p>
<p>Eine wichtige Lektion die ich gelernt habe: wenn die Sauce zu dünn wird <strong>nicht</strong> mit Stärke andicken. Die Stärke zieht sämtlichen Geschmack aus der Sauce.</p>
<ul>
<li>Sojaschnetzel in Wasser mit 1 EL Sojasauce kurz aufkochen und ziehen lassen</li>
<li>Brokkoli in mundgerecht Stücke zerschneiden</li>
<li>Knoblauch und Ingwer fein hacken</li>
<li>Erdmussmuß, Sojasauce, Ajvar, Agavendicksaft und ~150ml Wasser mischen und abschmecken</li>
<li>Sojaschnetzel abgießen, abwaschen und goldbraun anbraten</li>
<li>Knoblauch, Ingwer und Brokkoli hinzufügen und 1-2 Minuten mit anbraten</li>
<li>mit einem Schuss Wasser ablöschen, ~2 Minuten Brokkoli garen lassen</li>
<li>Erdnusssauce hinzufügen und ~5 Minuten köcheln lassen</li>
<li>Mit Reis oder Udon Nudeln servieren</li>
</ul>
<p><span class="hashtag"><a class="p-category" href="/tags/vegan/">#vegan</a></span> <span class="hashtag"><a class="p-category" href="/tags/rezept/">#rezept</a></span> <span class="hashtag"><a class="p-category" href="/tags/erdnuss/">#erdnuss</a></span> <span class="hashtag"><a class="p-category" href="/tags/brokkoli/">#brokkoli</a></span></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/moved-owl-blogs-to-github/</guid>
      <title>Moved owl-blogs to GitHub</title>
      <link>https://blog.libove.org/posts/moved-owl-blogs-to-github/</link>
      <pubDate>Sat, 18 May 2024 20:57:21 +0000</pubDate>
      <description><![CDATA[<p>Owl-blogs has reached a certain level of maturity and I think it is stable enough to be used by other people.
As I have a lot of articles on my blog any future changes have to be compatible or need an automated adjustment, even if I'm the only person using the software.</p>
<p>If you like to try out owl-blogs or even contribute to the project, the main location of the code is now on GitHub.</p>
<p><a href="https://github.com/H4kor/owl-blogs">H4kor/owl-blogs</a></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/rotkehlchen/</guid>
      <title>Rotkehlchen</title>
      <link>https://blog.libove.org/posts/rotkehlchen/</link>
      <pubDate>Sat, 18 May 2024 14:26:08 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC03796.jpg" class="u-photo">
    <img src="/thumbnail/DSC03796.jpg" alt="Rotkehlchen" />
</a>

<p>Ein Rotkehlchen sitzt auf einer verzierten Stange</p>


]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/frog-2/</guid>
      <title>Frog 2</title>
      <link>https://blog.libove.org/posts/frog-2/</link>
      <pubDate>Sun, 12 May 2024 09:53:07 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC08876.jpg" class="u-photo">
    <img src="/thumbnail/DSC08876.jpg" alt="Frog 2" />
</a>

<p>A frog sitting in a pond and croaking</p>


]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/frog-1/</guid>
      <title>Frog 1</title>
      <link>https://blog.libove.org/posts/frog-1/</link>
      <pubDate>Sun, 12 May 2024 09:43:42 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC08757.jpg" class="u-photo">
    <img src="/thumbnail/DSC08757.jpg" alt="Frog 1" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/crescent-moon/</guid>
      <title>Crescent Moon</title>
      <link>https://blog.libove.org/posts/crescent-moon/</link>
      <pubDate>Sun, 12 May 2024 08:18:40 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC08718.jpg" class="u-photo">
    <img src="/thumbnail/DSC08718.jpg" alt="Crescent Moon" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/calculating-the-complentary-color-in-css/</guid>
      <title>Calculating the complementary color in CSS</title>
      <link>https://blog.libove.org/posts/calculating-the-complentary-color-in-css/</link>
      <pubDate>Fri, 10 May 2024 17:54:42 +0000</pubDate>
      <description><![CDATA[<p>I'm currently rebuilding the design of my blog and want to use a complementary color palette.
As I'm not yet sure which colors I will use it would be ideal to automatically derive the secondary color from the primary color.</p>
<p>In the future this can be done using the <a href="https://caniuse.com/css-relative-colors">relative color feature</a> coming to CSS, but this isn't yet widely supported.</p>
<pre><code>    --primary: hsl(170, 66%, 28%);
    --secondary: hsl(from var(--primary) calc(h + 180) s l);
</code></pre>
<p>I figured out that you can achieve the same effect using the <a href="https://caniuse.com/mdn-css_types_color_color-mix">color-mix</a>. This is already supported by all major browsers.</p>
<pre><code>    --primary: hsl(170, 66%, 28%);
    --secondary: color-mix(in hsl longer hue, var(--primary), var(--primary) 50%);
</code></pre>
<p>The command mixes the primary color with itself in the HSL color space.
Additionally it is specified that it should use the longer pass along the hue axis.
As this will always be the 360° path, a mix of 50% will end up at the 180° complementary color to the primary color.
The saturation and lightness will stay the same.</p>
<p>This allows for fast testing of colors using the developer console:</p>
<p><a href="/media/color_mix.gif"><img src="/thumbnail/color_mix.gif" alt="Choosing a primary color automatically adjusts the secondary color."></a></p>
<p>This can also be used to create other color palettes by changing the percentage accordingly.</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/jukebox/</guid>
      <title>Jukebox</title>
      <link>https://blog.libove.org/posts/jukebox/</link>
      <pubDate>Mon, 29 Apr 2024 20:50:45 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC08626.jpg" class="u-photo">
    <img src="/thumbnail/DSC08626.jpg" alt="Jukebox" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/visualizing-data-on-maps-using-matplotlib-and-geopandas/</guid>
      <title>Visualizing Data on Maps using matplotlib and geopandas</title>
      <link>https://blog.libove.org/posts/visualizing-data-on-maps-using-matplotlib-and-geopandas/</link>
      <pubDate>Tue, 23 Apr 2024 18:18:50 +0000</pubDate>
      <description><![CDATA[<p>For visualizing the <a href="https://blog.libove.org/posts/visualisierung--heizgradtage-deutschland/">heating and cooling degree days of Germany</a> I had to learn how to plot maps.</p>
<h1>First Look</h1>
<p>My data source was a bunch of CSV files, which I combined into a single DataFrame, including latitude and longitude columns.
I've used <a href="https://geopandas.org/en/stable/">geopandas</a> to plot this data for a first visualization.
The DataFrame can be turned into a GeoDataFrame to create geometry information from the longitude and latitude columns.
It's important to specify the right <a href="https://en.wikipedia.org/wiki/Spatial_reference_system">coordinate reference system (CRS)</a> of your data.</p>
<p>With this conversion the data can be plotted using the <a href="https://geopandas.org/en/stable/docs/reference/api/geopandas.GeoDataFrame.plot.html"><code>plot</code></a> function of geopandas</p>
<pre><code>df = load_my_dataframe(...)
gdf = gpd.GeoDataFrame(
    df, geometry=gpd.points_from_xy(df[&quot;Longitude&quot;], df[&quot;Latitude&quot;]), crs=&quot;EPSG:4326&quot;
)
ax = gdf.plot(
    column=&quot;ValueToVisualize&quot;,
)
</code></pre>
<p><a href="/media/simple.png"><img src="/thumbnail/simple.png" alt="Simple visualization of geography data using points"></a></p>
<h1>Adding Boundaries</h1>
<p>The shape of Germany is already recognizable, but some more context would be beneficial.
For this reason I've added the borders of the German states to the plot.
The shape files of all European countries can be downloaded from <a href="https://ec.europa.eu/eurostat/web/gisco/geodata/reference-data/administrative-units-statistical-units/nuts#nuts21">eurostat</a> in various formats.
I've used the <em>&quot;Polygon (RG)&quot;</em> version and the <em>.shp</em> format.
The files contain the borders on different levels (country, state and regional borders).
With a few lines the borders can be added to the plot.</p>
<pre><code>border_file = &quot;NUTS_RG_01M_2021_3035.shp/NUTS_RG_01M_2021_3035.shp&quot;
eu_gdf = gpd.read_file(border_file)
eu_gdf.crs = &quot;EPSG:3035&quot;
gdf_de = eu_gdf[(eu_gdf.CNTR_CODE == &quot;DE&quot;) &amp; (eu_gdf.LEVL_CODE == 1)]
gdf_de.to_crs(&quot;EPSG:4326&quot;).boundary.plot(ax=ax, color=&quot;black&quot;)
</code></pre>
<p><a href="/media/borders.png"><img src="/thumbnail/borders.png" alt="visualization of geography data using points and state borders"></a></p>
<h1>Filling the Map</h1>
<p>Using points to visualize this data is not the best approach.
Instead I want to fill the entire map where each pixel is colored according to the nearest data point.
This can be achieved by computing a <a href="https://en.wikipedia.org/wiki/Voronoi_diagram">Voronoi Diagram</a>, where each data point is turned into a face.
I've tried to use the <a href="https://residentmario.github.io/geoplot/plot_references/plot_reference.html#voronoi">geoplot library</a> to compute the voronoi diagram, but it got stuck on my data.
Luckily <a href="https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.Voronoi.html">scipy also has a voronoi implementation</a>, but it's a bit more work to use.</p>
<pre><code># create a box around all data
min_lat = df[&quot;Latitude&quot;].min() - 1
max_lat = df[&quot;Latitude&quot;].max() + 1
min_lon = df[&quot;Longitude&quot;].min() - 1
max_lon = df[&quot;Longitude&quot;].max() + 1
boundarycoords = np.array(
    [
        [min_lat, min_lon],
        [min_lat, max_lon],
        [max_lat, min_lon],
        [max_lat, max_lon],
    ]
)
# convert our data point coordinates to a numpy array
coords = df[[&quot;Latitude&quot;, &quot;Longitude&quot;]].to_numpy()
all_coords = np.concatenate((coords, boundarycoords))
# compute voronoi
vor = scipy.spatial.Voronoi(points=all_coords)
# construct geometry from voronoi
polygons = [
    shapely.geometry.Polygon(vor.vertices[vor.regions[line]])
    for line in vor.point_region
    if -1 not in vor.regions[line]
]
voronois = gpd.GeoDataFrame(geometry=gpd.GeoSeries(polygons), crs=&quot;EPSG:4326&quot;)
# create dataframe
gdf = gpd.GeoDataFrame(
    df.reset_index(),
    geometry=voronois.geometry,
    crs=&quot;EPSG:4326&quot;,
)

# plot borders
gdf_de.to_crs(&quot;EPSG:4326&quot;).boundary.plot(ax=ax, color=&quot;black&quot;)
# clip geometry to inside of map
clip = eu_gdf[(eu_gdf.CNTR_CODE == &quot;DE&quot;) &amp; (eu_gdf.LEVL_CODE == 0)]
gdf = gdf.clip(clip.to_crs(&quot;EPSG:4326&quot;))
# plot data
gdf.plot(
    ax=ax,
    column=&quot;Monatsgradtage&quot;,
    cmap=&quot;Blues&quot;,
    legend=True,
)
</code></pre>
<p><a href="/media/voronoi.png"><img src="/thumbnail/voronoi.png" alt="Visualization using Voronoi diagram"></a></p>
<h1>Making it beautiful</h1>
<p>The plot is almost done, I only want to clean it up a bit.
As the axis don't serve any purpose here, I removed them.
Additionally I've added a title and a small text to the bottom to include the source in the graphic.</p>
<pre><code>ax.set_title(f&quot;Heizgradtage 2020&quot;, fontsize=20)
ax.axis(&quot;off&quot;)
ax.text(
    0.01,
    0.01,
    &quot;Quelle: https://www.dwd.de/DE/leistungen/gtz_kostenfrei/gtz_kostenfrei.html\nVisualisierung: Niko Abeler CC-BY-SA 4.0&quot;,
    ha=&quot;left&quot;,
    va=&quot;top&quot;,
    transform=ax.transAxes,
    alpha=0.5,
    fontsize=8,
)
</code></pre>
<p><a href="/media/final.png"><img src="/thumbnail/final.png" alt="Final Visualization"></a></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/visualisierung--heizgradtage-deutschland/</guid>
      <title>Visualisierung: Heizgradtage und Kühlgradstunden Deutschland</title>
      <link>https://blog.libove.org/posts/visualisierung--heizgradtage-deutschland/</link>
      <pubDate>Mon, 22 Apr 2024 20:14:27 +0000</pubDate>
      <description><![CDATA[<p><a href="https://de.wikipedia.org/wiki/Gradtagzahl">Heizgradtage</a> werden im Energiemanagement benutzt. Um den Heizbedarf über mehrere Jahre zu vergleichen muss der Effekt des Wetters herausgerechnet werden. Ansonsten vergleicht man jediglich das Wetter, da in kalten Wintern mehr geheizt werden muss. Dazu zählt man die Grad Celsius unter einem Schwellwert (meist 15°C in Deutschland).</p>
<p><strong>Als Beispiel:</strong> Ein Tag mit konstanter Temperatur von 5°C ergibt 10 Heizgradtage (<code>(15°C-5°C) * 1 Tag</code>). Ein Monat mit 10°C ergibt 300 Heizgradtage (<code>(15°C-5°C) * 30 Tage</code>)</p>
<p>Die Visualisierung zeigt die Heizgradtage der Jahre 2010 - 2023 in Deutschland. Je kälter (mehr Heizgradtage) es in einer Region ist, desto dunkler wird diese dargestellt. Die Visualisierung basiert auf den <a href="https://www.dwd.de/DE/leistungen/gtz_kostenfrei/gtz_kostenfrei.html">Daten des DWD</a>.</p>
<p><a href="/media/Heizgradtage_Deutschland.webp"><img src="/thumbnail/Heizgradtage_Deutschland.webp" alt=""></a></p>
<p>Parallel zu den Heizgradtagen gibt es auch die Kühlgradstunden. Hier werden die Stunden <strong>über</strong> einem Schwellwert (hier 18°C) gezählt. Eine Stunde mit 30°C ergibt somit 12 Kühlgradstunden. In dieser Visualisierung werden heiße Regionen in dunklerot dargestellt.</p>
<p><a href="/media/Kuehlgradstunden_Deutschland-1.webp"><img src="/thumbnail/Kuehlgradstunden_Deutschland-1.webp" alt=""></a></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/valley-of-dunes/</guid>
      <title>Valley of Dunes</title>
      <link>https://blog.libove.org/posts/valley-of-dunes/</link>
      <pubDate>Sat, 23 Mar 2024 18:50:42 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC07592.jpg" class="u-photo">
    <img src="/thumbnail/DSC07592.jpg" alt="Valley of Dunes" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/serenity/</guid>
      <title>Serenity</title>
      <link>https://blog.libove.org/posts/serenity/</link>
      <pubDate>Sat, 23 Mar 2024 18:49:49 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC07440.jpg" class="u-photo">
    <img src="/thumbnail/DSC07440.jpg" alt="Serenity" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/endurance/</guid>
      <title>Endurance</title>
      <link>https://blog.libove.org/posts/endurance/</link>
      <pubDate>Sat, 23 Mar 2024 18:45:31 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC07700.jpg" class="u-photo">
    <img src="/thumbnail/DSC07700.jpg" alt="Endurance" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/open-sourced--fedi-games/</guid>
      <title>Open Sourced: Fedi-Games</title>
      <link>https://blog.libove.org/posts/open-sourced--fedi-games/</link>
      <pubDate>Fri, 22 Mar 2024 18:07:37 +0000</pubDate>
      <description><![CDATA[<p>I've <a href="https://github.com/H4kor/fedi-games">open sourced fedi-games</a>, as <a href="https://blog.libove.org/posts/fediverse-games/">previously mentioned here</a>.
The code is still quiet rough as this is a learning project for me.
But maybe someone finds this useful to implement their own ActivityPub server.
Or maybe someone wants to build some new games :)</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/fediverse-games/</guid>
      <title>Fediverse Games</title>
      <link>https://blog.libove.org/posts/fediverse-games/</link>
      <pubDate>Wed, 20 Mar 2024 20:27:32 +0000</pubDate>
      <description><![CDATA[<p>In order to understand the ActivityPub protocol (at some point I want to make my blog available via ActivityPub) I've created a minimal server hosting game services for the Fediverse.</p>
<p>The service is currently hosted on <a href="https:games.rerere.org">games.rerere.org</a> and provides three games:</p>
<ul>
<li>Rock Paper Scissors</li>
<li>Tic-Tac-Toe</li>
<li>Bunkers, an artillery game</li>
</ul>
<p>The games can be played by mentioning the service and and your opponent in a note. I've mainly tested these with Mastodon but they should also work with other fediverse apps.</p>
<p><a href="/media/eab39bf0-701e-4821-b321-685d52d425f8.png"><img src="/thumbnail/eab39bf0-701e-4821-b321-685d52d425f8.png" alt="Screenshot from the Bunkers games"></a></p>
<p>I want to release the source code soon, but it still needs some clean up, documentation and testing to be useful to anyone.
The server is written in go and if you want to build something similar I've used these libraries to help with the ActivityPub implementation:</p>
<ul>
<li><a href="https://github.com/go-ap/activitypub">go-ap/activitypub</a></li>
<li><a href="https://github.com/go-ap/jsonld">go-ap/jsonld</a></li>
<li><a href="https://github.com/go-fed/httpsig">go-fed/httpsig</a></li>
</ul>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/d4a7e861-a1f1-4228-bd25-50167b58e559/</guid>
      <title></title>
      <link>https://blog.libove.org/posts/d4a7e861-a1f1-4228-bd25-50167b58e559/</link>
      <pubDate>Wed, 13 Mar 2024 18:37:59 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC07384.jpg" class="u-photo">
    <img src="/thumbnail/DSC07384.jpg" alt="" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/d1247faf-03c7-4926-8b26-59148c2c5759/</guid>
      <title></title>
      <link>https://blog.libove.org/posts/d1247faf-03c7-4926-8b26-59148c2c5759/</link>
      <pubDate>Wed, 13 Mar 2024 18:37:38 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC07373.jpg" class="u-photo">
    <img src="/thumbnail/DSC07373.jpg" alt="" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/inheritance-pattern-in-go/</guid>
      <title>Inheritance Pattern in Go</title>
      <link>https://blog.libove.org/posts/inheritance-pattern-in-go/</link>
      <pubDate>Sat, 24 Feb 2024 21:18:26 +0000</pubDate>
      <description><![CDATA[<p>Go isn't an object oriented language and doesn't have inheritance.
Sometimes I still want to model something in a similar way as I would in an object oriented languages.
My <a href="https://git.libove.org/h4kor/owl-blogs">blog software</a> has different types of &quot;entries&quot;, e.g. articles, images, recipes ...
This is modeled by an interface <code>Entry</code> which has to be implemented by each type of post.</p>
<pre><code>// truncated for simplicity
type Entry interface {
	ID() string
	Content() EntryContent
	PublishedAt() *time.Time
	Title() string
	SetID(id string)
	SetPublishedAt(publishedAt *time.Time)
}
</code></pre>
<p>One problem with this is, that I have to implement these functions for each type of entry.
Many of these implementation will be identical leading to a high degree of code duplication.
To avoid this I've created an <code>EntryBase</code>, which implements sensible defaults.</p>
<pre><code>type EntryBase struct {
	id          string
	publishedAt *time.Time
}

func (e *EntryBase) ID() string {
	return e.id
}

func (e *EntryBase) PublishedAt() *time.Time {
	return e.publishedAt
}

func (e *EntryBase) SetID(id string) {
	e.id = id
}

func (e *EntryBase) SetPublishedAt(publishedAt *time.Time) {
	e.publishedAt = publishedAt
}
</code></pre>
<p>With this &quot;base class&quot; the specific implementations only have to implement functions which differ between entry types.</p>
<pre><code>type Article struct {
	EntryBase
	meta ArticleMetaData
}

type ArticleMetaData struct {
	Title   string
	Content string
}


func (e *Article) Title() string {
	return e.meta.Title
}

func (e *Article) Content() model.EntryContent {
    // render content from markdown	
}
</code></pre>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/pilz-kichererbsen-risotto/</guid>
      <title>Pilz Kichererbsen Risotto</title>
      <link>https://blog.libove.org/posts/pilz-kichererbsen-risotto/</link>
      <pubDate>Fri, 05 Jan 2024 18:40:06 +0000</pubDate>
      <description><![CDATA[<small>
    
    Servings: <span class="p-yield">3 Portionen</span>
    , 

    

    
    Prep Time: <time class="dt-duration" value="45-60 Minuten">
        45-60 Minuten
    </time>
    
</small>
<br> <br>

<h2>Ingredients</h2>

<ul>
    
    <li class="p-ingredient">
        250g Risotto Reis
    </li>
    
    <li class="p-ingredient">
        1 Glas Kichererbsen
    </li>
    
    <li class="p-ingredient">
        400g Champignons
    </li>
    
    <li class="p-ingredient">
        2 kleine Zwiebel
    </li>
    
    <li class="p-ingredient">
        2 Knoblauchzehen
    </li>
    
    <li class="p-ingredient">
        25g getrocknete Pilze
    </li>
    
    <li class="p-ingredient">
        300ml Gemüsebrühe
    </li>
    
    <li class="p-ingredient">
        ½ Dose Kokosmilch
    </li>
    
    <li class="p-ingredient">
        1 EL Sojasoße
    </li>
    
    <li class="p-ingredient">
        1 EL Balsamico Essig
    </li>
    
    <li class="p-ingredient">
        Thymian und Pfeffer
    </li>
    
</ul>

<h2>Instructions</h2>
<p><a href="/media/pilz-kichererbsen.jpg"><img src="/thumbnail/pilz-kichererbsen.jpg" alt=""></a></p>
<ul>
<li>Getrocknete Pilze in 300 ml Wasser einweichen (~30 min). Aus Wasser nehmen und grob hacken. Wasser aufbewahren!</li>
<li>Pilze in Würfel schneiden und in Olivenöl goldbraun anbraten. Anschließen aus der Pfanne nehmen und beiseite Stellen.</li>
<li>Zwiebel und Knoblauch fein gehackt bei mittlerer Hitze leicht braun anbraten.</li>
<li>Reis und getrocknete Pilze hinzufügen und 1-2 Minuten mit anbraten. Mit Pilzewasser ablöschen.</li>
<li>Pilze und Kichererbsen hinzufügen.</li>
<li>Mischung aus Gemüsebrühe, Sojasoße, Essig und Thymian portionsweise hinzugeben.</li>
<li>Solange kochen und Flüssigkeit hinzufügen bis Reis fast gar ist.</li>
<li>Kokosmilch und Pfeffer hinzufügen. Für etwa 5 Minuten kochen bis gewünschte Konsistenz erreicht ist.</li>
</ul>
<p><span class="hashtag"><a class="p-category" href="/tags/vegan/">#vegan</a></span> <span class="hashtag"><a class="p-category" href="/tags/kichererbsen/">#kichererbsen</a></span> <span class="hashtag"><a class="p-category" href="/tags/pilze/">#pilze</a></span> <span class="hashtag"><a class="p-category" href="/tags/rezept/">#rezept</a></span></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/soap-bubble/</guid>
      <title>Soap Bubble</title>
      <link>https://blog.libove.org/posts/soap-bubble/</link>
      <pubDate>Sat, 30 Dec 2023 15:52:31 +0000</pubDate>
      <description><![CDATA[<a href="/media/signal-2023-12-30-16-13-09-389.jpg" class="u-photo">
    <img src="/thumbnail/signal-2023-12-30-16-13-09-389.jpg" alt="Soap Bubble" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/sheep/</guid>
      <title>Sheep</title>
      <link>https://blog.libove.org/posts/sheep/</link>
      <pubDate>Sat, 30 Dec 2023 15:51:02 +0000</pubDate>
      <description><![CDATA[<a href="/media/signal-2023-12-30-16-12-50-828-1.jpg" class="u-photo">
    <img src="/thumbnail/signal-2023-12-30-16-12-50-828-1.jpg" alt="Sheep" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/bhaal-spawn/</guid>
      <title>Bhaal Spawn</title>
      <link>https://blog.libove.org/posts/bhaal-spawn/</link>
      <pubDate>Sat, 30 Dec 2023 15:50:44 +0000</pubDate>
      <description><![CDATA[<a href="/media/signal-2023-12-30-16-12-50-828.jpg" class="u-photo">
    <img src="/thumbnail/signal-2023-12-30-16-12-50-828.jpg" alt="Bhaal Spawn" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/rooster/</guid>
      <title>Rooster</title>
      <link>https://blog.libove.org/posts/rooster/</link>
      <pubDate>Sat, 30 Dec 2023 15:46:41 +0000</pubDate>
      <description><![CDATA[<a href="/media/signal-2023-12-30-16-12-26-662-2.jpg" class="u-photo">
    <img src="/thumbnail/signal-2023-12-30-16-12-26-662-2.jpg" alt="Rooster" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/goose/</guid>
      <title>Goose</title>
      <link>https://blog.libove.org/posts/goose/</link>
      <pubDate>Sat, 30 Dec 2023 15:45:36 +0000</pubDate>
      <description><![CDATA[<a href="/media/signal-2023-12-30-16-12-26-662-1.jpg" class="u-photo">
    <img src="/thumbnail/signal-2023-12-30-16-12-26-662-1.jpg" alt="Goose" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/oldtimer/</guid>
      <title>Oldtimer</title>
      <link>https://blog.libove.org/posts/oldtimer/</link>
      <pubDate>Sat, 30 Dec 2023 15:44:25 +0000</pubDate>
      <description><![CDATA[<a href="/media/signal-2023-12-30-16-12-26-662.jpg" class="u-photo">
    <img src="/thumbnail/signal-2023-12-30-16-12-26-662.jpg" alt="Oldtimer" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/little-christmas-tree/</guid>
      <title>Little Christmas Tree</title>
      <link>https://blog.libove.org/posts/little-christmas-tree/</link>
      <pubDate>Thu, 21 Dec 2023 15:25:17 +0000</pubDate>
      <description><![CDATA[<a href="/media/christmas_tree.jpeg" class="u-photo">
    <img src="/thumbnail/christmas_tree.jpeg" alt="Little Christmas Tree" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/rust-gtk--creating-a-menu-bar-programmatically-with-gtk-rs/</guid>
      <title>Rust GTK: Creating a Menu Bar Programmatically with gtk-rs</title>
      <link>https://blog.libove.org/posts/rust-gtk--creating-a-menu-bar-programmatically-with-gtk-rs/</link>
      <pubDate>Thu, 21 Dec 2023 14:43:36 +0000</pubDate>
      <description><![CDATA[<p><em><strong>This guide is written for gtk-rs and GTK 4.6</strong></em></p>
<p>To create a menu bar in GTK4 we need a <code>MenuModel</code>, from which the bar is created.
The <code>MenuModel</code> can be derived from a <code>Menu</code> object.
<code>Menu</code> objects can have sub menues and and <code>MenuItems</code> as children, buidling a tree structure which represents the entire menu of the application.</p>
<p>Let's start at the end of our menu definition, by defining the root of the menu tree.
The root should only have sub menues as children.</p>
<pre><code class="language-rust">let menu = Menu::new();
menu.insert_submenu(0, Some(&quot;File&quot;), &amp;file_menu);
menu.insert_submenu(1, Some(&quot;Edit&quot;), &amp;edit_menu);
let menu_model: MenuModel = menu.into();
</code></pre>
<p>This is our root menu and derived model.
The first parameter determines the ordering of the menu entries.
The second parameter defines the name displayed in the menu bar and the last parameter is a reference to another <code>Menu</code> object.
In this example menu has two children &quot;File&quot; and &quot;Edit&quot; which are further menus we have to define.</p>
<p>The resulting menu bar will look similar to this:</p>
<p><a href="/media/menu_bar.png"><img src="/thumbnail/menu_bar.png" alt=""></a></p>
<p>The menus are <code>Menu</code> objects.
To add selectable entries, MenuItems are inserted.
The first parameter defines the ordering within the menu.
I tend to leave gaps in my ordering to allow insertions later, without changing all order numbers.
The <code>MenuItem</code> takes two (optional) strings as parameters.
The first string is the string shown in the menu. and the latter references an <a href="https://gtk-rs.org/gtk4-rs/stable/latest/book/actions.html">action</a>.
This action is executed if the menu item is selected.</p>
<pre><code class="language-rust">let file_menu = Menu::new();
file_menu.insert_item(0, &amp;MenuItem::new(Some(&quot;New Dungeon&quot;), Some(&quot;file.new&quot;)));
file_menu.insert_item(5, &amp;MenuItem::new(Some(&quot;Open ...&quot;), Some(&quot;file.open&quot;)));
file_menu.insert_item(10, &amp;MenuItem::new(Some(&quot;Save ...&quot;), Some(&quot;file.save&quot;)));
</code></pre>
<p>The resulting menu looks similar to this:</p>
<p><a href="/media/menu.png"><img src="/thumbnail/menu.png" alt=""></a></p>
<p>(Note that the shown shortcuts are automatically added when an <a href="https://gtk-rs.org/gtk4-rs/stable/latest/docs/gtk4/prelude/trait.GtkApplicationExt.html#method.set_accels_for_action">accelarator is defined for the action</a>).</p>
<p>To define the effect of selecting a menu item, actions have to be added to the window.
I insert actions into action groups to organize them by their main menu.
This code defines two actions in the &quot;file&quot; action group; &quot;file.new&quot; and &quot;file.open&quot;.
The name of the action group is defined when the group is added to the window.</p>
<pre><code class="language-rust">let file_actions = SimpleActionGroup::new();

let action_file_new = ActionEntry::builder(&quot;new&quot;)
    .activate(
        move |_group: &amp;SimpleActionGroup, _, _| {
            // Define behavior here
        },
    )
    .build();

let action_file_open = ActionEntry::builder(&quot;open&quot;)
    .activate(
        move |_group: &amp;SimpleActionGroup, _, _| {
            // Define behavior here
        },
    )
    .build();

file_actions.add_action_entries([
    action_file_new,
    action_file_open,
]);

window.insert_action_group(&quot;file&quot;, Some(&amp;file_actions));
</code></pre>
<p>For further organization of menu entries, sub menues can be used.
This uses the same method as used on the root menu.</p>
<pre><code class="language-rust">edit_menu.insert_submenu(20, Some(&quot;Change Mode&quot;), &amp;mode_menu);
</code></pre>
<p>This results in a sub menu similar to this</p>
<p><a href="/media/submenu.png"><img src="/thumbnail/submenu.png" alt=""></a></p>
<p>After defining the menu and deriving the <code>MenuModel</code> a <code>PopoverMenuBar</code> can be created and added to the application window.
This can be achieved by wrapping the content of the window in another box and adding the menu bar to this box.</p>
<pre><code class="language-rust">let menubar = PopoverMenuBar::from_model(Some(&amp;menu_model));

let window_box = gtk::Box::builder()
    .orientation(gtk::Orientation::Vertical)
    .build();
window_box.append(&amp;menubar);
window_box.append(&amp;main_box);

// Create a window
let window = ApplicationWindow::builder()
    .application(app)
    .child(&amp;window_box)
    ...
    .build():
</code></pre>
<p>The full code used in these examples can be found in the <a href="https://github.com/H4kor/dungeon-planner/blob/4eaeebd036c9c285f9b6667263649818c05d804b/src/main.rs#L141">DungeonPlanner Source Code</a></p>
<p><span class="hashtag"><a class="p-category" href="/tags/rust/">#rust</a></span> <span class="hashtag"><a class="p-category" href="/tags/gtk/">#gtk</a></span> <span class="hashtag"><a class="p-category" href="/tags/gtk4/">#gtk4</a></span> <span class="hashtag"><a class="p-category" href="/tags/gtkrs/">#gtkrs</a></span></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/example-dungeon--thieves-hideout/</guid>
      <title>Example Dungeon: Thieves Hideout</title>
      <link>https://blog.libove.org/posts/example-dungeon--thieves-hideout/</link>
      <pubDate>Tue, 19 Dec 2023 21:22:40 +0000</pubDate>
      <description><![CDATA[<p>Example Dungeon built with <a href="https://github.com/H4kor/dungeon-planner">DungeonPlanner</a> ( <a href="https://blog.libove.org/posts/dungeon-planner---first-release/">also see previous post</a>)</p>
<p><em><strong>(All texts are generated with ChatGPT)</strong></em></p>
<ul>
<li>Dungeon PDF: <a href="https://blog.libove.org/media/ThievesHideout.pdf">ThievesHideout.pdf</a></li>
<li>Dungeon File: <a href="https://blog.libove.org/media/ThievesHideout.dungeon">ThievesHideout.dungeon</a></li>
</ul>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/dungeon-planner---first-release/</guid>
      <title>Dungeon Planner - First Release</title>
      <link>https://blog.libove.org/posts/dungeon-planner---first-release/</link>
      <pubDate>Tue, 19 Dec 2023 20:16:55 +0000</pubDate>
      <description><![CDATA[<p>I've release a first &quot;pre-version&quot; and the source code of <a href="https://github.com/H4kor/dungeon-planner">DungeonPlanner on GitHub</a>.</p>
<p>DungeonPlanner is a small and simple tool I use to create and plan dungeons for tabletop games. It is game system agnostic and should be applicable to most tabletop games.</p>
<p>Feedback is appreciated as always :)</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/vegan-meat-pizza-/</guid>
      <title>vegan meat pizza </title>
      <link>https://blog.libove.org/posts/vegan-meat-pizza-/</link>
      <pubDate>Tue, 28 Nov 2023 11:49:19 +0000</pubDate>
      <description><![CDATA[<a href="/media/IMG_20231128_124727.jpg" class="u-photo">
    <img src="/thumbnail/IMG_20231128_124727.jpg" alt="vegan meat pizza " />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/rust-gtk4---mouse-events-in-drawingarea/</guid>
      <title>Rust GTK4 - Mouse Events in DrawingArea</title>
      <link>https://blog.libove.org/posts/rust-gtk4---mouse-events-in-drawingarea/</link>
      <pubDate>Wed, 15 Nov 2023 19:33:48 +0000</pubDate>
      <description><![CDATA[<p>For the app I'm currently working on I need to react to mouse inputs in a drawing area. I need the position of the mouse and the possibility to react to mouse clicks.</p>
<h2>Mouse Position</h2>
<p>The mouse position can be obtained by using an <a href="https://gtk-rs.org/gtk4-rs/stable/latest/docs/gtk4/struct.EventControllerMotion.html">EventControllerMotion</a>. This is added to the DrawingArea.</p>
<pre><code class="language-rust">let pos_controller = EventControllerMotion::new();
pos_controller.connect_motion(move |_con, x, y| {
    ...
});
drawing_area.add_controller(pos_controller);
</code></pre>
<h2>Mouse Clicks</h2>
<p>For each mouse button you want to &quot;observe&quot; a <a href="https://gtk-rs.org/gtk4-rs/stable/latest/docs/gtk4/struct.GestureClick.html">GestureClick</a> is created and added to the DrawingArea.</p>
<pre><code>let gesture_click = GestureClick::builder()
    .button(GDK_BUTTON_PRIMARY as u32)
    .build();

gesture_click.connect_pressed(move |_, _, _, _| {
    // create to mouse click
});
drawing_area.add_controller(gesture_click);
</code></pre>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/rust-gtk4---key-pressed/</guid>
      <title>Rust GTK4 - Key Pressed</title>
      <link>https://blog.libove.org/posts/rust-gtk4---key-pressed/</link>
      <pubDate>Wed, 15 Nov 2023 19:26:20 +0000</pubDate>
      <description><![CDATA[<p>I'm currently building an application using <a href="https://gtk-rs.org/">gtk-rs</a> using GTK4. To react to key presses the <a href="https://gtk-rs.org/gtk4-rs/git/docs/gtk4/struct.EventControllerKey.html">EventControllerKey</a> can be used. This is added as a controller to the main window.</p>
<pre><code class="language-rust">let control_key = gtk::EventControllerKey::new();

control_key.connect_key_pressed(move |_, key, _, _| {
    match key {
        gdk::Key::Right =&gt; { ... },
        gdk::Key::Left =&gt; { ... },
        gdk::Key::Up =&gt; { ... },
        gdk::Key::Down =&gt; { ... },
        _ =&gt; (),
    }
    glib::Propagation::Proceed
});
window.add_controller(control_key);
</code></pre>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/lonely-tree/</guid>
      <title>Lonely tree</title>
      <link>https://blog.libove.org/posts/lonely-tree/</link>
      <pubDate>Tue, 07 Nov 2023 15:29:07 +0000</pubDate>
      <description><![CDATA[<a href="/media/PXL_20231009_180731829.jpg" class="u-photo">
    <img src="/thumbnail/PXL_20231009_180731829.jpg" alt="Lonely tree" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/tent-under-tree/</guid>
      <title>Tent under tree</title>
      <link>https://blog.libove.org/posts/tent-under-tree/</link>
      <pubDate>Tue, 07 Nov 2023 15:28:05 +0000</pubDate>
      <description><![CDATA[<a href="/media/IMG_20231107_161145-01.jpeg" class="u-photo">
    <img src="/thumbnail/IMG_20231107_161145-01.jpeg" alt="Tent under tree" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/veganer-parmesan-/</guid>
      <title>Veganer Parmesan </title>
      <link>https://blog.libove.org/posts/veganer-parmesan-/</link>
      <pubDate>Sun, 10 Sep 2023 09:54:05 +0000</pubDate>
      <description><![CDATA[<small>
    

    
    Prep Time: <time class="dt-duration" value="5 Minuten ">
        5 Minuten 
    </time>
    
</small>
<br> <br>

<h2>Ingredients</h2>

<ul>
    
    <li class="p-ingredient">
        200g Cashews
    </li>
    
    <li class="p-ingredient">
        60g Hefeflocken
    </li>
    
    <li class="p-ingredient">
        1 TL Knoblauchpulver
    </li>
    
    <li class="p-ingredient">
        1 TL Salz
    </li>
    
</ul>

<h2>Instructions</h2>
<p>Alle Zutaten in einen Standmixer geben und solange zerkleinern bisam ein feines Pulver hat. Sobald die richtige Konsistenz erreicht ist, &quot;fließt&quot; der Parmesan nicht mehr richtig beim mixen und leicht klumpig wird.</p>
<p><span class="hashtag"><a class="p-category" href="/tags/vegan/">#vegan</a></span></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/pink-donut/</guid>
      <title>Pink Donut</title>
      <link>https://blog.libove.org/posts/pink-donut/</link>
      <pubDate>Sat, 09 Sep 2023 10:38:49 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC05755-01.jpeg" class="u-photo">
    <img src="/thumbnail/DSC05755-01.jpeg" alt="Pink Donut" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/3d-printed-toy-screws/</guid>
      <title>3D Printed Toy Screws</title>
      <link>https://blog.libove.org/posts/3d-printed-toy-screws/</link>
      <pubDate>Mon, 04 Sep 2023 17:30:21 +0000</pubDate>
      <description><![CDATA[<p>After seeing how I put a child barrier together my daughter is obsessed with screws. She will try to turn any screw she can find, using any item barely resembling a screwdriver. Naturally, she got her own set of tools :).</p>
<p><a href="/media/DSC05666.jpg"><img src="/thumbnail/DSC05666.jpg" alt="A Block with 2 screws. In front of it lie a toy screwdriver and wrench"></a></p>
<p>As the set didn't include any screws I've decided to print some for her. The screws are compatible with the wrench and screwdriver of the &quot;klein Bosch Work-Box&quot;. The holes of the block are loose enough to easily screw in the bolts.</p>
<p>I've used <a href="https://www.blender.org/">Blender</a> to create these. For my first try I've created my own screw using the &quot;Screw&quot; modifier applied to a triangle. This worked okay, but the resulting screw was too loose and would just slip into the hole. For the next try I used the plugin <a href="https://docs.blender.org/manual/en/latest/addons/add_mesh/boltfactory.html">&quot;Bolt Factory&quot;</a> to create the screw part and only kept my previously designed head.</p>
<p><strong>Things I've learned:</strong></p>
<ul>
<li>A 6-vertices circle with 18mm circumference is not the same as a 18mm screw head. The &quot;18mm&quot; refers to the distance between to edges, not two vertices.</li>
<li>3D printed screw holes need a lot more &quot;room&quot;. Simply scaling by the X and Y axis worked for my &quot;triangle screw&quot; but not the generated screw. Instead I had to thicken and thin the bands of the screw.</li>
</ul>
<p><a href="/media/ScrewHole.png"><img src="/thumbnail/ScrewHole.png" alt="How to modify a screw to be used as a hole"></a></p>
<p>All files can be used under the <a href="https://creativecommons.org/licenses/by/4.0/">CC-BY 4.0 License</a>.</p>
<ul>
<li><a href="/media/ToyScrews.blend">Blender File</a></li>
<li><a href="/media/ToyScrew.stl">Screw STL</a></li>
<li><a href="/media/ToyBlock1.stl">1-Hole Block</a></li>
<li><a href="/media/ToyBlock5.stl">5-Hole Block</a></li>
</ul>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/lamb/</guid>
      <title>Lamb</title>
      <link>https://blog.libove.org/posts/lamb/</link>
      <pubDate>Wed, 30 Aug 2023 15:57:28 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC02398.jpg" class="u-photo">
    <img src="/thumbnail/DSC02398.jpg" alt="Lamb" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/green-paprika---onion-pizza/</guid>
      <title>Green Paprika + Onion Pizza</title>
      <link>https://blog.libove.org/posts/green-paprika---onion-pizza/</link>
      <pubDate>Sun, 13 Aug 2023 19:35:11 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC04248.jpg" class="u-photo">
    <img src="/thumbnail/DSC04248.jpg" alt="Green Paprika &#43; Onion Pizza" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/materialized-views-in-postgresql/</guid>
      <title>Materialized Views in PostgreSQL</title>
      <link>https://blog.libove.org/posts/materialized-views-in-postgresql/</link>
      <pubDate>Sat, 12 Aug 2023 20:52:15 +0000</pubDate>
      <description><![CDATA[<p>At my hobby project <a href="https://podcast-standard.org">Podcast de facto Standard</a> I ran into problems querying the required data to render the report pages.
My first fix for this was to query and process all data (for all reports) once per day and to store the results as one big JSON object in an additional table.
This worked well until the server was running out of memory because too much data had to be loaded from the database to be processed in python.</p>
<p>I generally try to avoid database specific feature for as long as possible in my projects.
Ideally I'm able to use SQLite in local development and automated testing and only start using Postgres in the testing and production environment.
This works great with abstraction layers such as SQLAlchemy and Django and allows me to postpone the decision for a specific database system for as long as possible.
As a nice bonus it keeps the testing setup very simple as I can simple use an in memory SQLite.</p>
<p>The growing memory requirements were the point where I had to choose a specific database.
As I use PostgreSQL virtually always in production systems I looked into the options it provided to optimize my queries.</p>
<p>The main problems where cause by the episode data,  a ~9 GB table with +11 million rows.
I already had indexes in place but the queries for the <a href="https://podcast-standard.org/audio/">audio properties of podcasts</a> still took +1 minute per plot.
This meant that &quot;live&quot; queries where no longer possible and I needed a solution to store (partial) results.</p>
<p>I've chosen to use <a href="https://www.postgresql.org/docs/current/rules-materializedviews.html">materialized views</a> as they are a great fit for my problems.
The data presented on the page doesn't have to be updated with every newly analyzed audio file.
It is sufficient to get an update once per day (even once per week would be fine).
The transformations required can easily be done in SQL.</p>
<p>Materialized views behave almost like a regular table, but you cannot directly create or update rows in the table.
Instead the content is created from an SQL statement, executed once on creation or upon refreshing the view.</p>
<p>Creating a materialized view is simple:</p>
<pre><code class="language-sql">create materialized view pub_hist 
select date(publishing_date), count(*)
from episode e
group by date(publishing_date)
</code></pre>
<p>This command executes the selection query and stores the result in the view <code>pub_hist</code>.
Afterwards you can simply query from this view:</p>
<pre><code class="language-sql">select *
from pub_hist
where &quot;date&quot; &lt; now()
</code></pre>
<p>Other than normal <a href="https://www.postgresql.org/docs/15/sql-createview.html">views</a> the content of the materialized view is not updated automatically.
Instead you have to trigger a refresh, which will reevaluate the defined query and store the result:</p>
<pre><code class="language-sql">REFRESH MATERIALIZED VIEW pub_hist;
</code></pre>
<p>To refresh the views I simply run a daily (celery) job, which calls the refresh command for all views.</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/pizzasauce/</guid>
      <title>Pizzasauce</title>
      <link>https://blog.libove.org/posts/pizzasauce/</link>
      <pubDate>Sat, 12 Aug 2023 19:36:52 +0000</pubDate>
      <description><![CDATA[<small>
    
    Servings: <span class="p-yield">Für 4 Pizzen</span>
    , 

    

    
    Prep Time: <time class="dt-duration" value="30 Minuten">
        30 Minuten
    </time>
    
</small>
<br> <br>

<h2>Ingredients</h2>

<ul>
    
    <li class="p-ingredient">
        1 Dose Tomatenstücke
    </li>
    
    <li class="p-ingredient">
        2 Knoblauchzehen
    </li>
    
    <li class="p-ingredient">
        1 EL Olivenöl
    </li>
    
    <li class="p-ingredient">
        Oregano
    </li>
    
    <li class="p-ingredient">
        Thymian
    </li>
    
    <li class="p-ingredient">
        Salz und Zucker
    </li>
    
    <li class="p-ingredient">
        Optional: 1/2 Chili (getrocknet oder frisch)
    </li>
    
</ul>

<h2>Instructions</h2>
<ul>
<li>Knoblauch schälen und grob hacken</li>
<li>Auf mittlerer Hitze in einem kleinen Topf Olivenöl und Knoblauch erhitzen</li>
<li>Unter ständigem rühren den Knoblauch im Öl 2-3 Minuten garen. Der Knoblauch darf nicht braun werden
<ul>
<li>Chilis ebenfalls mitgaren falls gewollt</li>
</ul>
</li>
<li>Tomatenstücke dazugeben und zum köcheln bringen</li>
<li>Kräuter und eine Prise Salz und Zucker hinzugeben</li>
<li>Herd auf niedrige Hitze drehen und mit halb aufgelegtem Deckel für 20-30 Minuten einköcheln lassen</li>
<li>Mit dem Pürierstab zur gewollten Konsistenz zerkleinern</li>
<li>Heiß in ein verschließbarers Glas füllen, kann mindestens eine Woche im Kühlschrank gelagert werden</li>
</ul>
<p><a href="https://blog.libove.org/posts/1c5d68b1-ebe4-4d7d-9de3-5142a8b1b687">Rezept für Pizzateig</a></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/schnelle-pizzasauce/</guid>
      <title>Schnelle Pizzasauce</title>
      <link>https://blog.libove.org/posts/schnelle-pizzasauce/</link>
      <pubDate>Sat, 12 Aug 2023 19:26:34 +0000</pubDate>
      <description><![CDATA[<small>
    
    Servings: <span class="p-yield">Für eine Pizza</span>
    , 

    

    
    Prep Time: <time class="dt-duration" value="2 Minuten">
        2 Minuten
    </time>
    
</small>
<br> <br>

<h2>Ingredients</h2>

<ul>
    
    <li class="p-ingredient">
        3-4 EL Passata
    </li>
    
    <li class="p-ingredient">
        1/2 EL Olivenöl
    </li>
    
    <li class="p-ingredient">
        1 TL Oregano
    </li>
    
    <li class="p-ingredient">
        Prise Salz
    </li>
    
    <li class="p-ingredient">
        Prise Zucker
    </li>
    
</ul>

<h2>Instructions</h2>
<p>All Zutaten zusammengeben, gut mischen und direkt auf die Pizza. So einfach!</p>
<p>Wer mag kann auch noch etwas Knoblauchpulver geben.
Für eine pikante Soße einfach ein bisschen Chili aus der Mühle dazu.</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/-/</guid>
      <title>🐝</title>
      <link>https://blog.libove.org/posts/-/</link>
      <pubDate>Sat, 12 Aug 2023 18:42:06 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC05454-01.jpeg" class="u-photo">
    <img src="/thumbnail/DSC05454-01.jpeg" alt="🐝" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/snail/</guid>
      <title>Snail</title>
      <link>https://blog.libove.org/posts/snail/</link>
      <pubDate>Thu, 10 Aug 2023 19:58:49 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC05341.jpg" class="u-photo">
    <img src="/thumbnail/DSC05341.jpg" alt="Snail" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/a1662549-05bc-4a86-a642-7d324a624704/</guid>
      <title>Best Carrots Cake</title>
      <link>https://blog.libove.org/posts/a1662549-05bc-4a86-a642-7d324a624704/</link>
      <pubDate>Sun, 06 Aug 2023 10:46:34 +0000</pubDate>
      <description><![CDATA[<small>
    

    
</small>
<br> <br>

<h2>Ingredients</h2>

<ul>
    
    <li class="p-ingredient">
        400 g carrots
    </li>
    
    <li class="p-ingredient">
        Juice of a lemon
    </li>
    
    <li class="p-ingredient">
        175 ml sunflower oil
    </li>
    
    <li class="p-ingredient">
        400 g flour
    </li>
    
    <li class="p-ingredient">
        Pack of baking soda
    </li>
    
    <li class="p-ingredient">
        2 Packs of vanilla sugar
    </li>
    
    <li class="p-ingredient">
        200 g sugar
    </li>
    
    <li class="p-ingredient">
        100 g chopped almonds
    </li>
    
    <li class="p-ingredient">
        1/2 table spoon cinnamon
    </li>
    
    <li class="p-ingredient">
        2 cm ginger
    </li>
    
    <li class="p-ingredient">
        pinch of salt
    </li>
    
    <li class="p-ingredient">
        powdered sugar and lime juice
    </li>
    
</ul>

<h2>Instructions</h2>
<p><a href="/media/IMG_20190916_074656.jpg"><img src="/thumbnail/IMG_20190916_074656.jpg" alt=""></a></p>
<ul>
<li>Preheat your oven to 165°C</li>
<li>Grate the carrots and ginger and mix it with the oil and lemon juice</li>
<li>Mix all dry ingredients (flour, sugar, almonds, baking soda, vanilla sugar, cinnamon and salt) in another bowl.</li>
<li>Add the mixed dry ingredients to the carrots and mix everything thoroughly.</li>
<li>Fill everything in your cake pan and bake at 165°C for 60 minutes</li>
<li>Mix powdered sugar and lime juice to a viscous liquid and spread if over the finished cake</li>
</ul>
<p><span class="hashtag"><a class="p-category" href="/tags/vegan/">#vegan</a></span> <span class="hashtag"><a class="p-category" href="/tags/cake/">#cake</a></span></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/72805843-4448-4f32-a196-ca5ccb6e7790/</guid>
      <title>Aubergine &amp; Onions Pan</title>
      <link>https://blog.libove.org/posts/72805843-4448-4f32-a196-ca5ccb6e7790/</link>
      <pubDate>Sun, 06 Aug 2023 10:45:41 +0000</pubDate>
      <description><![CDATA[<small>
    
    Servings: <span class="p-yield">2 servings</span>
    

    

    
</small>
<br> <br>

<h2>Ingredients</h2>

<ul>
    
    <li class="p-ingredient">
        1 Aubergine (eggplant)
    </li>
    
    <li class="p-ingredient">
        3 spring onions
    </li>
    
    <li class="p-ingredient">
        1 small onion
    </li>
    
    <li class="p-ingredient">
        150 ml water
    </li>
    
    <li class="p-ingredient">
        1 table spoon soy sauce
    </li>
    
    <li class="p-ingredient">
        1 table spoon rice vinegar
    </li>
    
    <li class="p-ingredient">
        1 tea spoon curry paste
    </li>
    
    <li class="p-ingredient">
        2 tea spoons sugar
    </li>
    
    <li class="p-ingredient">
        1/2 tea spoon salt
    </li>
    
    <li class="p-ingredient">
        1-2 cm of ginger root
    </li>
    
    <li class="p-ingredient">
        3 cloves of garlic
    </li>
    
    <li class="p-ingredient">
        1 table spoon starch
    </li>
    
    <li class="p-ingredient">
        peanut oil
    </li>
    
    <li class="p-ingredient">
        rice
    </li>
    
</ul>

<h2>Instructions</h2>
<p><a href="/media/IMG_20190925_184137-01.jpeg"><img src="/thumbnail/IMG_20190925_184137-01.jpeg" alt=""></a></p>
<ul>
<li>Cut aubergine into large, long pieces (~4 cm long, ~2 cm thick)</li>
<li>Chop ginger and garlic, cut spring onions and onions into thin rings</li>
<li>Mix water, soy sauce, rice vinegar, sugar and salt</li>
<li>Mix starch with a small amount of water.</li>
<li>Fry in oil until golden brown. Press with spatula to release water</li>
<li>Put aubergine pieces aside and add new oil into the pan</li>
<li>Fry curry paste, garlic, ginger, green onions and onions for a short time</li>
<li>Add mixed water and aubergine pieces to the pan and cook everything for 2 minutes</li>
<li>Add starch to thicken the sauce</li>
<li>Serve with rice</li>
</ul>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/d57c6437-6c76-43b7-ac83-4a763f3b1aec/</guid>
      <title>Vegan Beluga Lentils Curry</title>
      <link>https://blog.libove.org/posts/d57c6437-6c76-43b7-ac83-4a763f3b1aec/</link>
      <pubDate>Sun, 06 Aug 2023 10:42:34 +0000</pubDate>
      <description><![CDATA[<small>
    
    Servings: <span class="p-yield">2-3 Servings</span>
    , 

    

    
    Prep Time: <time class="dt-duration" value="60 minutes">
        60 minutes
    </time>
    
</small>
<br> <br>

<h2>Ingredients</h2>

<ul>
    
    <li class="p-ingredient">
        1 Onion
    </li>
    
    <li class="p-ingredient">
        1 Garlic
    </li>
    
    <li class="p-ingredient">
        1 cm fresh ginger root
    </li>
    
    <li class="p-ingredient">
        1 Carrot
    </li>
    
    <li class="p-ingredient">
        3 table spoons Coconut oil
    </li>
    
    <li class="p-ingredient">
        1,5 table spoons red curry paste
    </li>
    
    <li class="p-ingredient">
        300 g Beluga lentils
    </li>
    
    <li class="p-ingredient">
        1 can tomatos (chunky or whole)
    </li>
    
    <li class="p-ingredient">
        1 can Coconut milk
    </li>
    
    <li class="p-ingredient">
        550 ml vegetable stock
    </li>
    
    <li class="p-ingredient">
        1 sweet potato
    </li>
    
    <li class="p-ingredient">
        2 table spoons lime juice
    </li>
    
    <li class="p-ingredient">
        Salt and pepper
    </li>
    
</ul>

<h2>Instructions</h2>
<p><strong>Origin:</strong> <a href="https://www.rewe.de/rezepte/veganes-beluga-linsen-curry/">https://www.rewe.de/rezepte/veganes-beluga-linsen-curry/</a></p>
<p><a href="/media/IMG_20190817_173235.jpg"><img src="/thumbnail/IMG_20190817_173235.jpg" alt=""></a></p>
<ul>
<li>Peel and chop the onions, garlic, ginger root and carrots.</li>
<li>Fry for 2 minutes. Add curry and lentils and fry for a short time</li>
<li>Add Tomatos, coconut milk and 200 ml vegetable stock.</li>
<li>Cook for 10 minutes and peel the sweet potato</li>
<li>Add sweet potato and cook for 20 minutes. Add vegetable stock periodically</li>
<li>Add lime juice. Season with salt and pepper.</li>
</ul>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/1c5d68b1-ebe4-4d7d-9de3-5142a8b1b687/</guid>
      <title>Pizzateig</title>
      <link>https://blog.libove.org/posts/1c5d68b1-ebe4-4d7d-9de3-5142a8b1b687/</link>
      <pubDate>Sun, 06 Aug 2023 09:44:44 +0000</pubDate>
      <description><![CDATA[<small>
    
    Servings: <span class="p-yield">2 Pizzas / 1 Blech</span>
    , 

    

    
    Prep Time: <time class="dt-duration" value="20 Minuten / 2-3 Tage">
        20 Minuten / 2-3 Tage
    </time>
    
</small>
<br> <br>

<h2>Ingredients</h2>

<ul>
    
    <li class="p-ingredient">
        350g 550 Mehl
    </li>
    
    <li class="p-ingredient">
        200ml Wasser
    </li>
    
    <li class="p-ingredient">
        10ml Olivenöl
    </li>
    
    <li class="p-ingredient">
        5g Salz
    </li>
    
    <li class="p-ingredient">
        ~5g frische Hefe
    </li>
    
</ul>

<h2>Instructions</h2>
<h3>Vorbereitung</h3>
<ul>
<li>Mehl und Salz in einer Schüssel mischen</li>
<li>Hefe in Wasser auflösen und zum Mehl geben, Olivenöl hinzufügen</li>
<li>Mit einer Gabel die Flüssigkeit untermischen</li>
<li>Sobald man mit der Gabel nicht mehr weiterkommt per Hand kneten bis ein homogener Teig entsteht</li>
<li>30 Minuten bei Raumtemperatur ruhen lassen und nocheinmal kurz kneten</li>
<li>1-3 Tage im Kühlschrank lagern, einmal täglich falten.</li>
</ul>
<p><a href="/media/pizzateig_001.jpg"><img src="/thumbnail/pizzateig_001.jpg" alt="Leerer Pizzateig"></a></p>
<h3>Backen</h3>
<ul>
<li>Teig in Portionen teilen, 275g für eine Pizza 550g für ein Blech</li>
<li>Kurz kneten um einen runden Teigballen zu formen</li>
<li>Mit einem Schuss Olivenöl in einer Schüssel 2-3 Stunden ruhen lassen</li>
<li>Teig per Hand strecken, <strong>nicht ausrollen</strong>, und belegen</li>
<li>Pizza ca. 8-10 Minuten (Blech 15-20 Minuten) bei 250°C backen.</li>
</ul>
<p><a href="/media/pizzateig_005.jpg"><img src="/thumbnail/pizzateig_005.jpg" alt="Fertige Pizza"></a></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/a3ef6076-03b8-4990-9baf-b1917ef4366f/</guid>
      <title>Squirrel</title>
      <link>https://blog.libove.org/posts/a3ef6076-03b8-4990-9baf-b1917ef4366f/</link>
      <pubDate>Fri, 04 Aug 2023 21:48:44 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC05197.jpg" class="u-photo">
    <img src="/thumbnail/DSC05197.jpg" alt="Squirrel" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/83073f5d-6a43-4132-b1cd-d9b347000014/</guid>
      <title>Arrow</title>
      <link>https://blog.libove.org/posts/83073f5d-6a43-4132-b1cd-d9b347000014/</link>
      <pubDate>Fri, 04 Aug 2023 21:38:03 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC05300.jpg" class="u-photo">
    <img src="/thumbnail/DSC05300.jpg" alt="Arrow" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/3a03fd64-154d-4e9d-9962-64bc34ff92d7/</guid>
      <title>What are you looking at?</title>
      <link>https://blog.libove.org/posts/3a03fd64-154d-4e9d-9962-64bc34ff92d7/</link>
      <pubDate>Fri, 04 Aug 2023 21:37:51 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC05198.jpg" class="u-photo">
    <img src="/thumbnail/DSC05198.jpg" alt="What are you looking at?" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2f01c418-1016-405b-9345-89b287b7297f/</guid>
      <title>Pond</title>
      <link>https://blog.libove.org/posts/2f01c418-1016-405b-9345-89b287b7297f/</link>
      <pubDate>Fri, 04 Aug 2023 21:37:35 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC05282.jpg" class="u-photo">
    <img src="/thumbnail/DSC05282.jpg" alt="Pond" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/81b5336f-0a82-46cf-8b3e-ca44e75aebc2/</guid>
      <title>Fishing Ducks</title>
      <link>https://blog.libove.org/posts/81b5336f-0a82-46cf-8b3e-ca44e75aebc2/</link>
      <pubDate>Fri, 04 Aug 2023 21:37:21 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC05310.jpg" class="u-photo">
    <img src="/thumbnail/DSC05310.jpg" alt="Fishing Ducks" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/811e4be8-d650-4fc4-bdb8-50c3a110dd3c/</guid>
      <title>Snail Child</title>
      <link>https://blog.libove.org/posts/811e4be8-d650-4fc4-bdb8-50c3a110dd3c/</link>
      <pubDate>Fri, 04 Aug 2023 21:37:00 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC05371.jpg" class="u-photo">
    <img src="/thumbnail/DSC05371.jpg" alt="Snail Child" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/94858148-aa16-44dd-9617-929f12b78e23/</guid>
      <title>Eating Goat</title>
      <link>https://blog.libove.org/posts/94858148-aa16-44dd-9617-929f12b78e23/</link>
      <pubDate>Sun, 30 Jul 2023 11:38:11 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC05131.jpg" class="u-photo">
    <img src="/thumbnail/DSC05131.jpg" alt="Eating Goat" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/49ddcaf1-4262-4095-9619-79ad10513e0b/</guid>
      <title>Hound</title>
      <link>https://blog.libove.org/posts/49ddcaf1-4262-4095-9619-79ad10513e0b/</link>
      <pubDate>Sun, 30 Jul 2023 11:37:52 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC05002.jpg" class="u-photo">
    <img src="/thumbnail/DSC05002.jpg" alt="Hound" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/ffc28003-95c8-41e6-9cbb-1b41ee8b54b0/</guid>
      <title>Thoughts on Analytics</title>
      <link>https://blog.libove.org/posts/ffc28003-95c8-41e6-9cbb-1b41ee8b54b0/</link>
      <pubDate>Sat, 29 Jul 2023 20:00:44 +0000</pubDate>
      <description><![CDATA[<p>Counting web page visitors accurately can be a challenging task, often bordering on the impossible.</p>
<p>There are two common methods to count visitors: extract the number from server logs or using an analytics software with an HTML snippet.
Both approaches have limitations which will result in distorted numbers.</p>
<p>Using analytics snippets tends to result in an underestimation of visitor counts. A significant portion of internet users have an AdBlocker, leading to their exclusion from the count. Additionally, certain users might disable JavaScript or refrain from loading resources from external domains, further contributing to the under counting issue.</p>
<p>On the other hand, relying solely on server logs tends to overstate the number of visitors. This is because a significant portion of server logs comprises entries generated by bots and crawlers, which should ideally be filtered out to arrive at an accurate visitor count. Yet, many bots attempt to conceal their identity, making this filtering process challenging. Furthermore, some legitimate visitors might be counted multiple times, for example when they switch networks and acquire new IP addresses.</p>
<p>The <strong>true</strong> number of visitors will lie somewhere between the value produced by your analytics snippet and a number calculated from server logs. Unfortunately there is no way to retrieve the &quot;true&quot; number of visitors.</p>
<p>In conclusion: There is no true visitor number, it always depends on how you count and your definition of a visitor.</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/bd4048ae-3a1e-4a4b-a17f-ac0b290626c3/</guid>
      <title>Podcast de facto Standard</title>
      <link>https://blog.libove.org/posts/bd4048ae-3a1e-4a4b-a17f-ac0b290626c3/</link>
      <pubDate>Thu, 27 Jul 2023 19:06:48 +0000</pubDate>
      <description><![CDATA[<p>I built a website and crawler to analyze the podcast ecosystem. The website contains various reports about the usage feed tags and audio properties.</p>
<p><a href="https://podcast-standard.org/">Podcast de facto Standard</a></p>
<p>For the information about tag usage, I used <a href="https://pypi.org/project/defusedxml/">defusedxml</a> and written some basic validators to check that the tags included are also valid according to there specification.</p>
<p>The audio analysis is done using <a href="https://ffmpeg.org/">ffmpeg</a> and <a href="https://ffmpeg.org/ffprobe.html">ffprobe</a>. I only extract basic features provided by these tools.</p>
<p>I hope this information is helpful to the podcasting community and people building their own podcasting system.</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/68ded576-dab3-4e6b-b4c1-1fff3dfea1be/</guid>
      <title>Flower Field</title>
      <link>https://blog.libove.org/posts/68ded576-dab3-4e6b-b4c1-1fff3dfea1be/</link>
      <pubDate>Fri, 21 Jul 2023 20:17:24 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC04652.jpg" class="u-photo">
    <img src="/thumbnail/DSC04652.jpg" alt="Flower Field" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/3cb42c00-5120-4c9a-bdf1-e43e03a75e06/</guid>
      <title>Spreading Wings</title>
      <link>https://blog.libove.org/posts/3cb42c00-5120-4c9a-bdf1-e43e03a75e06/</link>
      <pubDate>Fri, 21 Jul 2023 20:16:46 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC04398.jpg" class="u-photo">
    <img src="/thumbnail/DSC04398.jpg" alt="Spreading Wings" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/806a93d8-db2a-4a13-b62d-a5035a585af8/</guid>
      <title>New Avatar</title>
      <link>https://blog.libove.org/posts/806a93d8-db2a-4a13-b62d-a5035a585af8/</link>
      <pubDate>Wed, 19 Jul 2023 19:44:01 +0000</pubDate>
      <description><![CDATA[<a href="/media/new_avatar.jpg" class="u-photo">
    <img src="/thumbnail/new_avatar.jpg" alt="New Avatar" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/0b5501fc-05f8-470f-b95e-e631a19cea22/</guid>
      <title>Now running on Owl Blogs 2.0</title>
      <link>https://blog.libove.org/posts/0b5501fc-05f8-470f-b95e-e631a19cea22/</link>
      <pubDate>Wed, 19 Jul 2023 19:39:54 +0000</pubDate>
      <description><![CDATA[<p>Upgraded my blog to 2.0 of <a href="https://git.libove.org/h4kor/owl-blogs">Owl Blogs</a>.
This is a 100% rewrite of my blog software.
Some features, such as webmention, are not ported yet, but this new implementation allows me to be more flexible with features.</p>
<p>My next step will most likely be <a href="https://indieweb.org/POSSE">POSSE</a> as I often post photo to multiple sites manually.</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/playing-dog/</guid>
      <title>Playing Dog</title>
      <link>https://blog.libove.org/posts/playing-dog/</link>
      <pubDate>Wed, 05 Jul 2023 18:34:31 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC03656.jpg" class="u-photo">
    <img src="/thumbnail/DSC03656.jpg" alt="Playing Dog" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/frog/</guid>
      <title>Frog</title>
      <link>https://blog.libove.org/posts/frog/</link>
      <pubDate>Wed, 05 Jul 2023 18:34:06 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC03632.jpg" class="u-photo">
    <img src="/thumbnail/DSC03632.jpg" alt="Frog" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/pumpkin-flower/</guid>
      <title>Pumpkin Flower</title>
      <link>https://blog.libove.org/posts/pumpkin-flower/</link>
      <pubDate>Wed, 05 Jul 2023 18:33:47 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC03621.jpg" class="u-photo">
    <img src="/thumbnail/DSC03621.jpg" alt="Pumpkin Flower" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/i-like-turtles/</guid>
      <title>I like Turtles</title>
      <link>https://blog.libove.org/posts/i-like-turtles/</link>
      <pubDate>Wed, 05 Jul 2023 18:32:31 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC03546.jpg" class="u-photo">
    <img src="/thumbnail/DSC03546.jpg" alt="I like Turtles" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/elster-/</guid>
      <title>Elster </title>
      <link>https://blog.libove.org/posts/elster-/</link>
      <pubDate>Sun, 02 Jul 2023 08:23:30 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC03512-01.jpeg" class="u-photo">
    <img src="/thumbnail/DSC03512-01.jpeg" alt="Elster " />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/ants-on-a-strawberry-/</guid>
      <title>Ants on a Strawberry </title>
      <link>https://blog.libove.org/posts/ants-on-a-strawberry-/</link>
      <pubDate>Tue, 27 Jun 2023 10:39:19 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC03276_1-01.jpeg" class="u-photo">
    <img src="/thumbnail/DSC03276_1-01.jpeg" alt="Ants on a Strawberry " />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/wevelsburg-2/</guid>
      <title>Wevelsburg 2</title>
      <link>https://blog.libove.org/posts/wevelsburg-2/</link>
      <pubDate>Sun, 25 Jun 2023 16:55:29 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC03458.jpg" class="u-photo">
    <img src="/thumbnail/DSC03458.jpg" alt="Wevelsburg 2" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/wevelsburg-1/</guid>
      <title>Wevelsburg 1</title>
      <link>https://blog.libove.org/posts/wevelsburg-1/</link>
      <pubDate>Sun, 25 Jun 2023 16:55:14 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC03335.jpg" class="u-photo">
    <img src="/thumbnail/DSC03335.jpg" alt="Wevelsburg 1" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/bird-with-leaf/</guid>
      <title>Bird with leaf</title>
      <link>https://blog.libove.org/posts/bird-with-leaf/</link>
      <pubDate>Sat, 24 Jun 2023 16:07:45 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC03290_1-02.jpeg" class="u-photo">
    <img src="/thumbnail/DSC03290_1-02.jpeg" alt="Bird with leaf" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/sunbathing/</guid>
      <title>Sunbathing</title>
      <link>https://blog.libove.org/posts/sunbathing/</link>
      <pubDate>Tue, 20 Jun 2023 20:16:09 +0000</pubDate>
      <description><![CDATA[<a href="/media/IMG_20221119_135622~2.jpg" class="u-photo">
    <img src="/thumbnail/IMG_20221119_135622~2.jpg" alt="Sunbathing" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/raindrops/</guid>
      <title>Raindrops</title>
      <link>https://blog.libove.org/posts/raindrops/</link>
      <pubDate>Tue, 20 Jun 2023 20:14:01 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC02838.JPG" class="u-photo">
    <img src="/thumbnail/DSC02838.JPG" alt="Raindrops" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/blocks/</guid>
      <title>Blocks</title>
      <link>https://blog.libove.org/posts/blocks/</link>
      <pubDate>Mon, 19 Jun 2023 11:32:57 +0000</pubDate>
      <description><![CDATA[<a href="/media/IMG_20230522_062404_102.jpg" class="u-photo">
    <img src="/thumbnail/IMG_20230522_062404_102.jpg" alt="Blocks" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/glasfaser-spargel/</guid>
      <title>Glasfaser Spargel</title>
      <link>https://blog.libove.org/posts/glasfaser-spargel/</link>
      <pubDate>Sun, 18 Jun 2023 17:59:23 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC02851.jpg" class="u-photo">
    <img src="/thumbnail/DSC02851.jpg" alt="Glasfaser Spargel" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/take-off/</guid>
      <title>Take Off</title>
      <link>https://blog.libove.org/posts/take-off/</link>
      <pubDate>Sun, 18 Jun 2023 17:12:55 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC03005.jpg" class="u-photo">
    <img src="/thumbnail/DSC03005.jpg" alt="Take Off" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/beschilderungsproblem-/</guid>
      <title>Beschilderungsproblem </title>
      <link>https://blog.libove.org/posts/beschilderungsproblem-/</link>
      <pubDate>Sun, 18 Jun 2023 17:06:27 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC02860.JPG" class="u-photo">
    <img src="/thumbnail/DSC02860.JPG" alt="Beschilderungsproblem " />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/on-a-stroll/</guid>
      <title>On a Stroll</title>
      <link>https://blog.libove.org/posts/on-a-stroll/</link>
      <pubDate>Thu, 04 May 2023 18:42:45 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC02419_01.jpg" class="u-photo">
    <img src="/thumbnail/DSC02419_01.jpg" alt="On a Stroll" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/taking-a-nap/</guid>
      <title>Taking a Nap</title>
      <link>https://blog.libove.org/posts/taking-a-nap/</link>
      <pubDate>Thu, 04 May 2023 18:42:19 +0000</pubDate>
      <description><![CDATA[<a href="/media/DSC02409_01.jpg" class="u-photo">
    <img src="/thumbnail/DSC02409_01.jpg" alt="Taking a Nap" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/3d-printed-case-for-esp8266--dht22/</guid>
      <title>3D Printed Case for ESP8266 + DHT22</title>
      <link>https://blog.libove.org/posts/3d-printed-case-for-esp8266--dht22/</link>
      <pubDate>Sun, 12 Feb 2023 18:43:57 +0000</pubDate>
      <description><![CDATA[<p>After finishing my <a href="https://blog.libove.org/posts/building-a-prusa-mini---end-of-session-3-done/">3D printer</a> and running the first <a href="https://blog.libove.org/posts/first-3d-print-benchy-/">test print</a>, I wanted to make a case for a temperature and humidity sensor.
I use the <a href="https://en.wikipedia.org/wiki/ESP8266">ESP8266</a>, a small microcontroller with WiFi capabilities, and the <a href="https://learn.adafruit.com/dht">DHT22</a> as the temperature and humidity sensor.</p>
<p>I started by building small test print using <a href="https://www.blender.org/">Blender</a>, to test if the ESP8266 could be mounted on small pins.
Luckily I took this step, as I mismeasured the spacing of the pins, which could be easily fixed at this stage.
Additionally the print showed that my printer was stringing a lot, which I fixed by <a href="https://blog.libove.org/posts/note-10/">changing the retraction settings</a>.</p>
<p><a href="/media/test_print.jpg"><img src="/thumbnail/test_print.jpg" alt="Test print"></a></p>
<p>The next step was to design the case.
For this I simply added 1mm thick walls around the mounting.
Using boolean functions I've added holes for the USB port and the reset button.
Additionally I've added a lid to the case, which was simply placed on a 0.5mm lip in the case.</p>
<p><a href="/media/first_case.jpg"><img src="/thumbnail/first_case.jpg" alt="First design"></a></p>
<p>The design revealed some issues, which I fixed in the next iteration.
The holes in the lid were too small and the printer was having trouble printing them.
The USB port cutout had to be moved and increased slightly.
Additionally the box was to small to fit the ESP8266 and the DHT22 and I added 1cm to the height of the box.
These changes led to the second design.</p>
<p><a href="/media/second_case.jpg"><img src="/thumbnail/second_case.jpg" alt="Second design"></a></p>
<p>I used this design for a day, which revealed another issue.
The sensor was reporting too high temperatures, by about 3 degrees Celsius.
This was caused by the fact that the DHT22 sensor was too close to the ESP8266.
I had to separate the two components by increasing the size of the box and adding a separation wall.</p>
<p>As I had to redesign the case anyway, I also redesigned the lid.
I changed the design of the holes from cubes to octagons.
The lid is now held in place by protusions which fit into holes in the case.
I added additional holes to the case, where the DHT22 sensor would be located.</p>
<p><a href="/media/third_case.jpg"><img src="/thumbnail/third_case.jpg" alt="Third design"></a></p>
<p>The case still has some potential for improvement, but I'm happy with the current design for now.</p>
<p><a href="/media/assembled.jpg"><img src="/thumbnail/assembled.jpg" alt="Assembled case"></a></p>
<p>The files for the case can be found below and can be used under the <a href="https://creativecommons.org/licenses/by-sa/4.0/">CC BY-SA 4.0</a> license.</p>
<ul>
<li>Blender file: <a href="/media/case.blend">case.blend</a></li>
<li>STL files:
<ul>
<li>Case <a href="/media/case.stl">case.stl</a></li>
<li>Lid <a href="/media/lid.stl">lid.stl</a></li>
</ul>
</li>
</ul>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/first-3d-print-benchy-/</guid>
      <title>First 3D Print: Benchy </title>
      <link>https://blog.libove.org/posts/first-3d-print-benchy-/</link>
      <pubDate>Sat, 04 Feb 2023 20:49:27 +0000</pubDate>
      <description><![CDATA[<a href="/media/PXL_20230202_184444540.jpg" class="u-photo">
    <img src="/thumbnail/PXL_20230202_184444540.jpg" alt="First 3D Print: Benchy " />
</a>

<p>This was the first 3D print I did, to test the printed.
The result is great, stringing being the only issue.
I will now try out different retraction settings to reduce stringing.</p>


]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/building-a-prusa-mini---end-of-session-3-done/</guid>
      <title>Building a Prusa Mini+ - End of Session 3 (Done)</title>
      <link>https://blog.libove.org/posts/building-a-prusa-mini---end-of-session-3-done/</link>
      <pubDate>Sat, 04 Feb 2023 20:47:14 +0000</pubDate>
      <description><![CDATA[<a href="/media/PXL_20230131_213408231.jpg" class="u-photo">
    <img src="/thumbnail/PXL_20230131_213408231.jpg" alt="Building a Prusa Mini&#43; - End of Session 3 (Done)" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/mini-pizza-for-my-daugther/</guid>
      <title>Mini Pizza for my daugther</title>
      <link>https://blog.libove.org/posts/mini-pizza-for-my-daugther/</link>
      <pubDate>Mon, 30 Jan 2023 19:08:23 +0000</pubDate>
      <description><![CDATA[<a href="/media/PXL_20230114_120453218.jpg" class="u-photo">
    <img src="/thumbnail/PXL_20230114_120453218.jpg" alt="Mini Pizza for my daugther" />
</a>

<p>I baked pizza for dinner. My daughter got her own piece with tomatoes.</p>


]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/building-a-prusa-mini---end-of-session-2/</guid>
      <title>Building a Prusa Mini+ - End of Session 2</title>
      <link>https://blog.libove.org/posts/building-a-prusa-mini---end-of-session-2/</link>
      <pubDate>Mon, 30 Jan 2023 19:04:25 +0000</pubDate>
      <description><![CDATA[<a href="/media/2.jpg" class="u-photo">
    <img src="/thumbnail/2.jpg" alt="Building a Prusa Mini&#43; - End of Session 2" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/building-a-prusa-mini---end-of-session-1/</guid>
      <title>Building a Prusa Mini+ - End of Session 1</title>
      <link>https://blog.libove.org/posts/building-a-prusa-mini---end-of-session-1/</link>
      <pubDate>Mon, 30 Jan 2023 19:04:12 +0000</pubDate>
      <description><![CDATA[<a href="/media/1.jpg" class="u-photo">
    <img src="/thumbnail/1.jpg" alt="Building a Prusa Mini&#43; - End of Session 1" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/dinkelvollkornbrot-mit-nssen/</guid>
      <title>Dinkelvollkornbrot mit Nüssen</title>
      <link>https://blog.libove.org/posts/dinkelvollkornbrot-mit-nssen/</link>
      <pubDate>Thu, 26 Jan 2023 15:40:46 +0000</pubDate>
      <description><![CDATA[<small>
    
    Servings: <span class="p-yield">1</span>
    , 

    

    
    Prep Time: <time class="dt-duration" value="1h">
        1h
    </time>
    
</small>
<br> <br>

<h2>Ingredients</h2>

<ul>
    
    <li class="p-ingredient">
        600g Dinkelvollkornmehl
    </li>
    
    <li class="p-ingredient">
        150g Hafermilch
    </li>
    
    <li class="p-ingredient">
        150g Walnüsse
    </li>
    
    <li class="p-ingredient">
        150g Haselnüsse
    </li>
    
    <li class="p-ingredient">
        30g Leinsamen
    </li>
    
    <li class="p-ingredient">
        70g Basismüsli
    </li>
    
    <li class="p-ingredient">
        6g Hefe
    </li>
    
    <li class="p-ingredient">
        Sonnenblumkerne
    </li>
    
</ul>

<h2>Instructions</h2>
<p><a href="/media/dinkelbrot.jpg"><img src="/thumbnail/dinkelbrot.jpg" alt="Dinkelvollkornbrot mit Nüssen"></a></p>
<p>Das Rezept basiert auf dem <a href="https://www.ploetzblog.de/2013/05/29/leserwunsch-dinkelvollkornbrot-mit-buttermilch/">Dinkelvollkornbrot mit Buttermilch</a> von Plötzblog und ist eine vegane Anpassung. Die Saaten können variiert werden, benutzt einfach was ihr da habt und mögt.</p>
<h3>Vorteig</h3>
<ul>
<li>300g Dinkelvollkornmehl</li>
<li>150g Hafermilch</li>
<li>150g Wasser</li>
<li>15g Salz</li>
</ul>
<h3>Saaten</h3>
<ul>
<li>150g Walnüsse</li>
<li>150g Haselnüsse</li>
<li>30g Leinsamen</li>
<li>70g Basismüsli</li>
<li>400g Wasser</li>
</ul>
<h3>Hautteig</h3>
<ul>
<li>300g Dinkelvollkornmehl</li>
<li>110g Wasser</li>
<li>6g Hefe</li>
<li>Vorteig und Saaten</li>
<li>Sonnenblumkerne zum rollen</li>
</ul>
<h2>Zubereitung</h2>
<ul>
<li>
<p>Vorteig und Saaten mindestens 2 Stunden ruhen lassen.</p>
</li>
<li>
<p>Hauptteig per Hand kneten bis homogen.</p>
</li>
<li>
<p>Hauptteig 2 Stunden gehen lassen und währenddessen 3 mal falten</p>
</li>
<li>
<p>In Sonnenblumkerne rollen und in gefettete Backform geben.</p>
</li>
<li>
<p>Mindestens 12 Stunden in Kühlschrank gehen lassen, Volumen sollte sich etwas verdoppeln. Ansonsten vor dem backen bei Raumtemperatur weiter gehen lassen.</p>
</li>
<li>
<p>Vor dem Backen befeuchten und bei 180°C Ober-/Unterhitze für 90 Minuten backen.</p>
</li>
<li>
<p>Anschließend 10 Minuten ohne Form backen.</p>
</li>
</ul>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/winter-park/</guid>
      <title>Winter Park</title>
      <link>https://blog.libove.org/posts/winter-park/</link>
      <pubDate>Sat, 14 Jan 2023 10:34:32 +0000</pubDate>
      <description><![CDATA[<a href="/media/Winter%20Park.jpg" class="u-photo">
    <img src="/thumbnail/Winter%20Park.jpg" alt="Winter Park" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/blumenkohl-wings-mit-ofenkartoffeln/</guid>
      <title>Blumenkohl-Wings mit Ofenkartoffeln</title>
      <link>https://blog.libove.org/posts/blumenkohl-wings-mit-ofenkartoffeln/</link>
      <pubDate>Thu, 12 Jan 2023 20:40:43 +0000</pubDate>
      <description><![CDATA[<small>
    
    Servings: <span class="p-yield">2</span>
    , 

    

    
    Prep Time: <time class="dt-duration" value="45 Minutes">
        45 Minutes
    </time>
    
</small>
<br> <br>

<h2>Ingredients</h2>

<ul>
    
    <li class="p-ingredient">
        ½ großen Blumenkohl
    </li>
    
    <li class="p-ingredient">
        4-6 Kartoffeln
    </li>
    
    <li class="p-ingredient">
        100ml Hafermilch
    </li>
    
    <li class="p-ingredient">
        1TL Paprikapulver
    </li>
    
    <li class="p-ingredient">
        1/2TL Korianderpulver
    </li>
    
    <li class="p-ingredient">
        Weizenmehl (550)
    </li>
    
    <li class="p-ingredient">
        Paniermehl
    </li>
    
    <li class="p-ingredient">
        Rosmarin
    </li>
    
    <li class="p-ingredient">
        Olivenöl
    </li>
    
    <li class="p-ingredient">
        Salz und Pfeffer
    </li>
    
</ul>

<h2>Instructions</h2>
<h3>Blumenkohl-Wings</h3>
<ul>
<li>Blumenkohl in mungerechte Stücke zerlegen</li>
<li>Paprikapulver, Koriander, Salz und Pfeffer mischen</li>
<li>Mit Mehl andicken bis man einen dicken Backteig hat</li>
<li>Blumenkohl in Teig und Paniermehl wenden und auf Backblech legen</li>
<li>Mit Olivenöl beträufeln</li>
</ul>
<h3>Kartoffeln</h3>
<ul>
<li>Kartoffeln schälen und in Würfel oder Wedges schneiden</li>
<li>Mit Salz, Rosmarin und Olivenöl mischen und auf das Blech geben</li>
</ul>
<p>Alles bei 190° für 30-40 Minuten backen.</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/ai-posioning-is-already-a-thing/</guid>
      <title>Re: </title>
      <link>https://blog.libove.org/posts/ai-posioning-is-already-a-thing/</link>
      <pubDate>Thu, 15 Dec 2022 18:35:21 +0000</pubDate>
      <description><![CDATA[Reply to: <a href="https://blog.libove.org/posts/ai-posioning/">https://blog.libove.org/posts/ai-posioning/</a>

<p>Artists on Artstation started to post <a href="https://arstechnica.com/information-technology/2022/12/artstation-artists-stage-mass-protest-against-ai-generated-artwork/">&quot;No AI&quot; images</a></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/ai-posioning/</guid>
      <title>AI Posioning</title>
      <link>https://blog.libove.org/posts/ai-posioning/</link>
      <pubDate>Tue, 13 Dec 2022 19:41:07 +0000</pubDate>
      <description><![CDATA[<p>The age of AI generated “content” (I hate that word!) has come. <a href="https://stability.ai/blog/stable-diffusion-public-release">Stable Diffusion</a>, <a href="https://openai.com/dall-e-2/">Dall-E</a> and <a href="https://midjourney.com/">Midjourney</a> are used to create <a href="https://www.nytimes.com/2022/09/02/technology/ai-artificial-intelligence-artists.html">artworks winning contests</a>. Especially Stable Diffusion, sharing their weights openly, has led to a rapid adoption of AI image tools into workflows and tools used by artists every day. <a href="https://openai.com/blog/chatgpt/">ChatGPT</a> can create high-quality texts which can no longer be distinguished from texts written by humans. Platforms like <a href="https://meta.stackoverflow.com/questions/421831/temporary-policy-chatgpt-is-banned">StackOverflow have felt the need to temporarily ban their usage</a>, until they figure out how to deal with this.</p>
<p>AIs (still) rely on <a href="https://waxy.org/2022/08/exploring-12-million-of-the-images-used-to-train-stable-diffusions-image-generator/">vast amounts</a> <a href="https://www.springboard.com/blog/data-science/machine-learning-gpt-3-open-ai/#Summary">of training data</a>. To build improved models, larger and larger datasets are required. Models will have to be retrained periodically to include new concepts in the model. Image generators have to be updated to be adjusted to the ever shifting taste of the humans using them. Text generators have to learn the latest <a href="https://en.wikipedia.org/wiki/Meme">memes</a> to stay relevant.</p>
<p>The explosive popularity of AI generated content will lead to new challenges when building new datasets. I see two ways how the progress on these generative models can be slowed down or even stopped entirely by their own popularity.</p>
<h2>Self Poisoning</h2>
<p>The datasets used for this a generally created by scraping the required data from the internet. This means everything posted to Reddit, Twitter and the rest of the internet is collected, cleaned and prepared for training AIs. The cleaning part will become harder and harder with each new iteration of generative models, as you want to avoid content created by the AI in your training set.</p>
<p>An AI system trained on data created by the system itself will not become better, as the information in the data is already known by the system. If the data used are valid examples, this might just waste computation time while training data model. But any poor examples will degrade the performance of the resulting model.</p>
<p>If a considerable portion of the training data comprises examples created by a previous version of a generative model, the next model will learn to recreate the style and errors of the previous model.</p>
<h2>Adversarial Poisoning</h2>
<p>A lot of artists hate generative models, especially for their ability to <a href="https://twitter.com/Kelly_McKernan/status/1601277420518387712">mimic the style</a> of individual artists. This has led some to hide their artworks, because they don’t want to provide even more data to these systems. If the concerns of artists are ignored, some might try to sabotage the systems which steal their art style.</p>
<p>A common technique for using Stable Diffusion is to give a prompt like “A cat sitting on a bench, <strong>by Artist X</strong>”. For artists with a large portfolio, this creates results which, on a first glance, could have been created by these artists. As datasets are generated automatically, it might be possible to introduce adversarial examples into the training data which destroy such prompts.</p>
<p>Artists might publish decoy “artworks” on their feeds. These decoys would be easily recognisable by humans, but scraping systems would include them in the training sets. If an artist has more decoys than real artworks associated with their name, the AI system will mimic the style of the decoy.</p>
<p>Artworks can be published with surrounding noise. Instead of just publishing an image, the image might be extended by random frames. The descriptions could be extended by additional nonsense descriptions.</p>
<p>All these countermeasures can be circumvented, but this will be expensive. For general models, trained on massive data models, such cleaning measures will most likely be too expensive. However, fine tuning a model only requires a relatively small training set. Cleaning such a dataset for a single artist will be a simple task. The poisoning will only be a minor inconvenience for people creating such specialized models.</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/aliases-for-working-with-django/</guid>
      <title>Aliases for working with Django</title>
      <link>https://blog.libove.org/posts/aliases-for-working-with-django/</link>
      <pubDate>Wed, 07 Dec 2022 14:08:56 +0000</pubDate>
      <description><![CDATA[<p>I'm using this aliases to work with Django. They save me a few seconds per command and lower the burden of trying something in the shell by a tiny amount :).</p>
<pre><code>alias vv=&quot;. venv/bin/activate&quot;
alias djs=&quot;python manage.py shell&quot;
alias djr=&quot;python manage.py runserver&quot;
alias djmm=&quot;python manage.py makemigrations&quot;
alias djmi=&quot;python manage.py migrate&quot;
</code></pre>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/note-8/</guid>
      <title>Re: </title>
      <link>https://blog.libove.org/posts/note-8/</link>
      <pubDate>Sun, 04 Dec 2022 14:21:45 +0000</pubDate>
      <description><![CDATA[Reply to: <a href="https://blog.libove.org/posts/first-article-written-with-editor/">https://blog.libove.org/posts/first-article-written-with-editor/</a>

<p>Extended the editor to allow replies. Still have to add a scraper to fill in the title of the referenced page automatically.</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/advent-of-code-2022/</guid>
      <title>Advent of Code 2022</title>
      <link>https://blog.libove.org/posts/advent-of-code-2022/</link>
      <pubDate>Fri, 02 Dec 2022 06:52:30 +0000</pubDate>
      <description><![CDATA[<p>I'm taking part in <a href="https://adventofcode.com/">Advent of Code 2022</a> and post my <a href="https://github.com/H4kor/advent-of-code">solutions on GitHub</a>.</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/first-article-written-with-editor/</guid>
      <title>First Article Written with Editor</title>
      <link>https://blog.libove.org/posts/first-article-written-with-editor/</link>
      <pubDate>Tue, 29 Nov 2022 20:36:21 +0000</pubDate>
      <description><![CDATA[<p>This is my first article written with an editor on my website!</p>
<p>Up until now I wrote all my articles either directly on the server or on my PC.
For this I use a convoluted system, partially working on the server, partially on my PC with git and ssh in between.
As this process is tedious it keeps me from writing short updates.</p>
<p>The online editor is still every limited but I will extend it as needed.
Additionally I've adjusted some of the internal structure of the blog to allow for different types of post.
This will allow me to also write short notes, which will not show up in the main feed.</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/graph-force-python-library-for-embedding-large-graphs-in-2d-space-using-force-directed-layouts/</guid>
      <title>graph-force: Python library for embedding large graphs in 2D space, using force-directed layouts</title>
      <link>https://blog.libove.org/posts/graph-force-python-library-for-embedding-large-graphs-in-2d-space-using-force-directed-layouts/</link>
      <pubDate>Mon, 28 Nov 2022 19:05:00 +0000</pubDate>
      <description><![CDATA[<p>I've published my graph embedding library <a href="https://github.com/H4kor/graph-force">graph_force</a> on GitHub and <a href="https://pypi.org/project/graph-force/">PyPi</a>.
I wrote about the <a href="https://blog.libove.org/posts/layouting-large-social-graphs/">process of building this a few days ago</a>.</p>
<p>My own algorithm turned out to be quiet slow when compared to <a href="https://networkx.org/">networkx</a>.
For this reason I also reimplemented the networkx algorithm, but with multithreading support.
The most important feature for me was the ability to load the graph from a binary file.
While networkx used to much data while ingesting the graph data, I can effortlessly write it to file and load it in graph_force.</p>
<p>At the moment this library fulfils my needs, but with publishing I commit to maintaining it.
Maybe this is useful to someone else.</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/reply-the-carcinization-of-go-programs/</guid>
      <title>Re: The carcinization of Go programs</title>
      <link>https://blog.libove.org/posts/reply-the-carcinization-of-go-programs/</link>
      <pubDate>Wed, 23 Nov 2022 08:55:28 +0000</pubDate>
      <description><![CDATA[Reply to: <a href="https://xeiaso.net/blog/carcinization-golang">https://xeiaso.net/blog/carcinization-golang</a>

<p>Creative usage of web assembly as a &quot;universal&quot; binary, running on every machine.
I'm currently using both Rust and Go. Will keeps this in mind if I ever want to combine them :)</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/community-tweeting-it/</guid>
      <title>Community Tweeting It</title>
      <link>https://blog.libove.org/posts/community-tweeting-it/</link>
      <pubDate>Tue, 22 Nov 2022 18:28:01 +0000</pubDate>
      <description><![CDATA[<p>I just realised I never wrote about this small project I created back in 2020.
The show <a href="https://en.wikipedia.org/wiki/Community_(TV_series)">Community</a> had a bunch of Twitter account for the characters on the show.
They wrote tweets in characters every now and then, which added some additional character interactions to the show.
A few times the twitter interactions were also referenced in the show.</p>
<p>I've scraped the tweets of all (known) accounts and created <a href="https://community-tweets.rerere.org/#/">a website to browse all community tweets.</a>
I tried to assign the tweets to episodes based on the airing dates.
With the current acquisition of Twitter by Musk and the following &quot;turbulences&quot; it might be a good idea to check the project again to see if more data can be preserved.
It would be a shame if this piece of my favorite show disappeared forever.</p>
<p><a href="https://github.com/H4kor/community-tweeting-it">The source and prepared datasets can be found on GitHub.</a></p>
<p>The show also add a homepage for the fictional community college.
Sadly the website is no longer available but it's in <a href="https://web.archive.org/web/20091227230203/http://www.greendalecommunitycollege.com/">the internet archive</a>.
Sadly the videos of the <a href="https://web.archive.org/web/20091230074247/http://greendalecommunitycollege.com/av_dept/index_student_films.shtml">A/V Department</a> did not survive.</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/layouting-large-social-graphs/</guid>
      <title>Building a Spring Graph Layout Algorithm in Rust</title>
      <link>https://blog.libove.org/posts/layouting-large-social-graphs/</link>
      <pubDate>Sat, 19 Nov 2022 15:41:46 +0000</pubDate>
      <description><![CDATA[<p>After <a href="https://blog.libove.org/posts/scraping-mastodon-peer-lists/">scraping &quot;all&quot; Mastodon instances</a>, I wanted to visualize the graph of instances.
My expectation is that this is a (quiet dense) social graph.
To bring order in such a graph a <a href="https://en.wikipedia.org/wiki/Force-directed_graph_drawing">Force-directed graph model</a> can be used.
I previously used the <a href="https://networkx.org/documentation/stable/reference/generated/networkx.drawing.layout.spring_layout.html">networkx implementation</a> for this.
However the peers graph I currently have is too large, with 24007 nodes and over 80 million edges.
When trying to import this into networkx I simply run out of memory (16GB).
After asking for advise on <a href="https://chaos.social/@h4kor/109336880151543461">Mastodon</a>, I tried out <a href="https://gephi.org/">gephi</a>, which also ran out of memory before loading the entire graph.</p>
<p>Because I still want to visualize this graph, I've decided to write my on <a href="https://git.libove.org/h4kor/tidy_graphs">graph layouting program</a>.
For the first implementation I followed the <a href="https://i11www.iti.kit.edu/_media/teaching/winter2011/graphdrawing/vl02.pdf">slides of a lecture at KIT</a>.
This gave me some janky but promising results, as I was able to load my graph and an iteration only required ~10 seconds.
To validate my implementation I created a debug graph, consisting of 2000 nodes with 4 clusters of different sizes.</p>
<p><video width="100%" src="/media/initial_model.mp4" alt="Animation of a force directed graph algorithm. There are still points jumping around." controls></video></p>
<p>After this first implementation I took pen and paper and thought about the problem a bit.
This lead to an improved version, with a simpler model leading to faster execution times and quicker convergence.</p>
<p><a href="/media/spring_model.jpeg"><img src="/thumbnail/spring_model.jpeg" alt="Picture of notes about spring models"></a></p>
<p><video width="100%" src="/media/my_model.mp4" alt="Animation of a force directed graph algorithm." controls></video></p>
<p>Embedding the mastodon instances graph, is still challenging.
The algorithm creates oscillation in the graph, which I suspect are introduced by one (or multiple) large cliques.
<strong>I will post an update soon.</strong></p>
<p><strong>Update:</strong></p>
<p>Bonus image of the <a href="https://networkx.org/documentation/stable/reference/generated/networkx.generators.social.florentine_families_graph.html">florentine families graph</a>:</p>
<p><a href="/media/florentine.png"><img src="/thumbnail/florentine.png" alt="Picture of the florentine families graph"></a></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/scraping-mastodon-peer-lists/</guid>
      <title>Scraping Mastodon peer lists</title>
      <link>https://blog.libove.org/posts/scraping-mastodon-peer-lists/</link>
      <pubDate>Sun, 13 Nov 2022 15:28:02 +0000</pubDate>
      <description><![CDATA[<p>Over the weekend I've decided to explore the <a href="https://en.wikipedia.org/wiki/Fediverse">Fediverse</a> a bit, especially <a href="https://joinmastodon.org/">Mastodon</a>.
As the network is decentralized, my first step was to create a list of &quot;instances&quot; (servers running mastodon).
Luckily mastodon has an <a href="https://docs.joinmastodon.org/methods/instance/">API endpoint</a> from which you get a list of peers for an instance.
Using this API I was able to start with a single instance and find over 89503 mastodon servers (only ~24000 of these also worked and exposed their peers).</p>
<p>For my first steps I used <a href="https://requests.readthedocs.io/en/latest/">requests</a>.
As this was too slow, with many servers not responding, I switched to <a href="https://docs.aiohttp.org/en/stable/">aiohttp</a> to run multiple concurrent requests.</p>
<p>I used a many loop which started new request tasks and waited for these tasks to finish.
Whenever a request task finished I wrote the result in an SQLite database for later analyses and started another request task.
This achieved a good throughput and crawled the &quot;entire&quot; mastodon world in a few hours.</p>
<p>I might add a cleaned up version of the script in the future.</p>
<h4>Things I learned:</h4>
<ul>
<li>first time seriously working with <code>asyncio</code> and <code>aiohttp</code> in python.</li>
<li><a href="https://docs.python.org/3/library/asyncio-task.html#asyncio.wait"><code>asyncio.wait</code></a> returns a <code>done</code> and <code>pending</code>. I used this to process done requests and afterwards replaced my tasks list with the <code>pending</code> return value.</li>
<li>sqlite does not work well with asyncio. This is why stored the results in the main loop.</li>
<li>I found it easiest to catch all error in my request function and return a tuple with an success indicator (<code>return result, success</code>) instead of allowing raised errors in the the async task.</li>
</ul>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/implemented-indieauth/</guid>
      <title>Implemented IndieAuth</title>
      <link>https://blog.libove.org/posts/implemented-indieauth/</link>
      <pubDate>Mon, 07 Nov 2022 19:56:44 +0000</pubDate>
      <description><![CDATA[<p>I just finished implementing <a href="https://indieweb.org/IndieAuth">IndieAuth</a>, which allows me to login to sites and tools, which support this, using my blog.
The main reason I've implemented this is because I want to use <a href="https://indieweb.org/Micropub">Micropub</a> to write and publish.
At the moment creating an new entry is a bit cumbersome.
It's ok for longer posts but it prevents me from doing things like bookmarks or check ins.</p>
<p>If you want to implement IndieAuth yourself I recommend using the <a href="https://indieauth.spec.indieweb.org/">living standard document</a> directly.
I've started using the wiki page as reference and later realised that it was outdated (the outdated content has been removed in the meantime).
IndieAuth is basically OAuth2 with a few additions and conventions. If you are already familiar with OAuth2 you probably not have many problems implementing this.
My (first) implementation can be found in <a href="https://git.libove.org/h4kor/owl-blogs/pulls/25">this pull request</a>.</p>
<p>Before I continue with Micropub, I have to do some refactoring as the code is still a bit rough around the edges.</p>
<p>Things I learned while implementing IndieAuth:</p>
<ul>
<li>CSRF does not require server state. You can use the <a href="https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie">Double Submit Cookie</a></li>
<li>Learned how <a href="https://oauth.net/2/pkce/">PKCE</a> works and that it is rather simple.</li>
<li>Got a deeper understanding of OAuth2 in general. Previously I always used server and client libraries (as any responsible developer should!)</li>
</ul>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/analyse-multiple-log-archives-with-goaccess/</guid>
      <title>Analyse multiple log archives with goaccess</title>
      <link>https://blog.libove.org/posts/analyse-multiple-log-archives-with-goaccess/</link>
      <pubDate>Tue, 01 Nov 2022 21:29:01 +0000</pubDate>
      <description><![CDATA[<ul>
<li>If you only want to load archives, use <code>zcat /path/to/logs/*.log.gz | goaccess</code></li>
<li>You can add the current log file to the end <code>zcat /path/to/logs/*.log.gz | goaccess /path/to/logs/current.log</code></li>
<li><em>Note:</em> It might take same time until all files are loaded.</li>
<li>You can add other flags to the goaccess command, such as the <a href="https://blog.libove.org/posts/use-goaccess-with-caddy-logs/">log format</a></li>
</ul>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/use-goaccess-with-caddy-logs/</guid>
      <title>Use goaccess with caddy logs</title>
      <link>https://blog.libove.org/posts/use-goaccess-with-caddy-logs/</link>
      <pubDate>Tue, 01 Nov 2022 21:24:05 +0000</pubDate>
      <description><![CDATA[<ul>
<li>Make sure you have a <a href="https://goaccess.io/download">recent version of goaccess</a>
<ul>
<li>Note: Ubuntu ships an older version, which might not yet support the caddy log format</li>
</ul>
</li>
<li>Use <code>goaccess --log-format CADDY /path/to/logs</code></li>
</ul>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/weekly-2022-42--2022-43/</guid>
      <title>Weekly 2022-42 + 2022-43</title>
      <link>https://blog.libove.org/posts/weekly-2022-42--2022-43/</link>
      <pubDate>Mon, 31 Oct 2022 19:35:03 +0000</pubDate>
      <description><![CDATA[<ul>
<li>Video: <a href="https://www.youtube.com/watch?v=8Ab3ArE8W3s">Stop writing dead programs</a></li>
<li>Specification for where to place &quot;dot files&quot; to keep home directories clean. <a href="https://xdgbasedirectoryspecification.com/">https://xdgbasedirectoryspecification.com/</a></li>
<li><a href="https://www.gamedeveloper.com/design/how-to-make-a-roguelike">How to Make a Roguelike</a></li>
</ul>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/weekly-2022-41/</guid>
      <title>Weekly 2022-41</title>
      <link>https://blog.libove.org/posts/weekly-2022-41/</link>
      <pubDate>Thu, 20 Oct 2022 16:47:20 +0000</pubDate>
      <description><![CDATA[<ul>
<li>Read <a href="https://www.goodreads.com/book/show/10127019-the-lean-startup">The Lean Startup by Eric Ries</a>
<ul>
<li>My take aways:
<ul>
<li>Start to measure success metrics from day 1</li>
<li>Avoid measurements which increase naturally (i.e. number of registered users).</li>
<li>Ensure you can do split tests from the start</li>
<li>Build &quot;sloppy&quot; feature and check if they improve your success metrics. If they don't, question why and throw that feature away</li>
</ul>
</li>
</ul>
</li>
<li>Finished the <a href="https://www.goodreads.com/series/57134-jean-le-flambeur">Jean le Flambeur Series by Hannu Rajaniemi</a>. 5/5
<ul>
<li>The books play in a future version of the solar system where virtually everything is turned into smart matter and the boundaries between &quot;reality&quot; and the virtual world are blurred to the point where they almost no longer matter. The series tells the story of &quot;Jean le Flambeur&quot; and &quot;Mieli&quot;, who travel the solar system to fulfil their duties to a Goddess. The journey takes them to Mars, Earth and Saturn.</li>
</ul>
</li>
</ul>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/go-custom-datetime-format-in-yaml/</guid>
      <title>Go: Custom Date/Time Format in YAML</title>
      <link>https://blog.libove.org/posts/go-custom-datetime-format-in-yaml/</link>
      <pubDate>Mon, 17 Oct 2022 19:00:24 +0000</pubDate>
      <description><![CDATA[<p>The <a href="https://pkg.go.dev/gopkg.in/yaml.v2"><em>yaml</em> package</a> in <a href="https://go.dev">Go</a> uses a default time format for marshalling and will only accept this format when reading a <em>yaml</em> file.
This is ok if you use the serializer to exchange data between programs, but quickly falls apart when user generated files have to be parsed.
As I'm using <em>yaml</em> headers in Markdown files (and <em>yaml</em> files in various other places) to generate this blog, I needa more robust and user friendly way to set publishing dates.
Ideally the blog software should accept any time format.</p>
<p>For this guide I will assume that we have a <em>yaml</em> format like this:</p>
<pre><code class="language-yaml">title: &quot;How to parse YAML dates&quot;
date: 17-10-2022
</code></pre>
<p>The corresponding definition in Go looks like this:</p>
<pre><code class="language-go">type Data struct {
	Title   string    `yaml:&quot;title&quot;`
	Date    time.Time `yaml:&quot;date&quot;`
}
</code></pre>
<p>When we try to unmarshal the above <em>yaml</em> it will result in an error: <code>parsing time &quot;17-10-2022&quot; as &quot;2006-01-02T15:04:05Z07:00&quot;: cannot parse &quot;0-2022&quot; as &quot;2006&quot;</code>.
To support this format the unmarshal behavior of the <code>Data</code> struct has to be overwritten.</p>
<pre><code class="language-go">func (d *Data) UnmarshalYAML(unmarshal func(interface{}) error) error {
	type T struct {
		Title string `yaml:&quot;title&quot;`
	}
	type S struct {
		Date string `yaml:&quot;date&quot;`
	}

	// parse non-time fields
	var t T
	if err := unmarshal(&amp;t); err != nil {
		return err
	}
	d.Title = t.Title

	// parse time field
	var s S

	if err := unmarshal(&amp;s); err != nil {
		return err
	}

	possibleFormats := []string{
		&quot;02-01-2006&quot;,
		time.Layout,
		time.ANSIC,
	}
	var found_time bool = false
	for _, format := range possibleFormats {
		if t, err := time.Parse(format, s.Date); err == nil {
			d.Date = t
			found_time = true
			break
		}
	}

	if !found_time {
		return fmt.Errorf(&quot;could not parse date: %s&quot;, s.Date)
	}

	return nil
}
</code></pre>
<p>Let's break this down.</p>
<pre><code class="language-go">func (d *Data) UnmarshalYAML(unmarshal func(interface{}) error) error {
</code></pre>
<p>In order to control the unmarhalling of a <em>yaml</em> file, we have to provide an <code>UnmarshalYAML</code> <a href="https://pkg.go.dev/gopkg.in/yaml.v2#Unmarshaler">function for the struct</a>.
This function is passed an <code>unmarshal</code> function which can be used to parse arbitrary structs.
We will use this function multiple times to retrieve different parts of the <em>yaml</em> file.</p>
<pre><code class="language-go">	type T struct {
		Title string `yaml:&quot;title&quot;`
	}
	type S struct {
		Date string `yaml:&quot;date&quot;`
	}
</code></pre>
<p>Next we separate our <code>Data</code> struct into two parts; one for everything we just want to keep as it is and one for the date field.
Note that the date field is using <code>string</code> as its type, as we want to do the time parsing ourselves.</p>
<pre><code class="language-go">	// parse non-time fields
	var t T
	if err := unmarshal(&amp;t); err != nil {
		return err
	}
	d.Title = t.Title
</code></pre>
<p>For all fields that we want to keep untouched, we just have to call the <code>unmarshal</code> function and, if no error occured, copy the data to our data struct.
To parse the date we start similarily, by getting the time string.</p>
<pre><code class="language-go">	// parse time field
	var s S

	if err := unmarshal(&amp;s); err != nil {
		return err
	}
</code></pre>
<p>Aftwards the function loops over all formats that we want to support and check if the string matches any format.
The list of possible formats can be extended.
I kept it short in this example.
You can find the full list I use at the bottom of the tutorial.</p>
<pre><code class="language-go">	possibleFormats := []string{
		&quot;02-01-2006&quot;,
		time.Layout,
		time.ANSIC,
	}
	var found_time bool = false
	for _, format := range possibleFormats {
		if t, err := time.Parse(format, s.Date); err == nil {
			d.Date = t
			found_time = true
			break
		}
	}
</code></pre>
<p>Finally, we check if any format matched.</p>
<pre><code class="language-go">	if !found_time {
		return fmt.Errorf(&quot;could not parse date: %s&quot;, s.Date)
	}

	return nil
</code></pre>
<p>Now our <code>Data</code> struct will accept any time format we add to the <code>possibleFormats</code> list.
This is the full list I currently use in my blog software, the latest code can be found in the <a href="https://git.libove.org/h4kor/owl-blogs">owl-blogs</a> project.</p>
<pre><code class="language-go">	possibleFormats := []string{
		&quot;2006-01-02&quot;,
		time.Layout,
		time.ANSIC,
		time.UnixDate,
		time.RubyDate,
		time.RFC822,
		time.RFC822Z,
		time.RFC850,
		time.RFC1123,
		time.RFC1123Z,
		time.RFC3339,
		time.RFC3339Nano,
		time.Stamp,
		time.StampMilli,
		time.StampMicro,
		time.StampNano,
	}
</code></pre>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/1665430315-new-markup/</guid>
      <title>New Markup</title>
      <link>https://blog.libove.org/posts/1665430315-new-markup/</link>
      <pubDate>Mon, 10 Oct 2022 19:31:55 +0000</pubDate>
      <description><![CDATA[<p>I've worked on my microformat markup and added more author information to the <code>h-entry</code>.
Additionally I've added support for replys to my Markdown files, by defining the reply-to in the meta data like such:</p>
<pre><code>reply:
  url: &quot;https://borghal.blog/note/2022/09/11/194932/&quot;
</code></pre>
<p>Hope this works for your webmention implementation :)</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/1665169868-mushroom-photos1/</guid>
      <title>Mushrooms</title>
      <link>https://blog.libove.org/posts/1665169868-mushroom-photos1/</link>
      <pubDate>Fri, 07 Oct 2022 19:11:08 +0000</pubDate>
      <description><![CDATA[<a href="/media/mushrooms1.jpg" class="u-photo">
    <img src="/thumbnail/mushrooms1.jpg" alt="Mushrooms" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/1665169868-mushroom-photos3/</guid>
      <title>Mushrooms</title>
      <link>https://blog.libove.org/posts/1665169868-mushroom-photos3/</link>
      <pubDate>Fri, 07 Oct 2022 19:11:08 +0000</pubDate>
      <description><![CDATA[<a href="/media/mushrooms3.jpg" class="u-photo">
    <img src="/thumbnail/mushrooms3.jpg" alt="Mushrooms" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/1665169868-mushroom-photos2/</guid>
      <title>Mushrooms</title>
      <link>https://blog.libove.org/posts/1665169868-mushroom-photos2/</link>
      <pubDate>Fri, 07 Oct 2022 19:11:08 +0000</pubDate>
      <description><![CDATA[<a href="/media/mushrooms2.jpg" class="u-photo">
    <img src="/thumbnail/mushrooms2.jpg" alt="Mushrooms" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/1665169663-weekly-2022-36-40/</guid>
      <title>Weekly 2022 36-40</title>
      <link>https://blog.libove.org/posts/1665169663-weekly-2022-36-40/</link>
      <pubDate>Fri, 07 Oct 2022 19:07:43 +0000</pubDate>
      <description><![CDATA[<ul>
<li>Got struck down by a nasty cold for almost two weeks</li>
<li>Went on vacation at the baltic sea
<ul>
<li><a href="https://blog.libove.org/posts/1665169868-mushroom-photos/">Shot some nice photos of mushrooms</a></li>
</ul>
</li>
<li>Added author information to posts on the blog</li>
</ul>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/1662967304-reply-to-leveling-up-my-indieweb-game-2/</guid>
      <title>Re: </title>
      <link>https://blog.libove.org/posts/1662967304-reply-to-leveling-up-my-indieweb-game-2/</link>
      <pubDate>Mon, 12 Sep 2022 07:21:44 +0000</pubDate>
      <description><![CDATA[Reply to: <a href=""></a>

<p><a href="https://borghal.blog/note/2022/09/11/194932/" class="u-in-reply-to">Leveling up my IndieWeb game</a></p>
<p>My markup is still a bit rough and minimal.
I guess you could use the name in my <code>h-card</code>, but I definitely should add the author to the <code>h-entry</code>.</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/1662922088-reply-to-leveling-up-my-indieweb-game/</guid>
      <title>Re: </title>
      <link>https://blog.libove.org/posts/1662922088-reply-to-leveling-up-my-indieweb-game/</link>
      <pubDate>Sun, 11 Sep 2022 18:48:08 +0000</pubDate>
      <description><![CDATA[Reply to: <a href=""></a>

<p><a href="https://borghal.blog/blog/2022-09/implementing-webmentions/" class="u-in-reply-to">Leveling up my IndieWeb game</a></p>
<p>I'm currently in the same process. I've just retrieved and sent my first webmentions two weeks ago.
I really like the way you integrated the mentions into you blog, my implementation is still very basic in that regard.</p>
<p>Micropub is also high on my list, after attending the <a href="https://www.hwclondon.co.uk/">Homebrew Website Club</a> this week, as I'm still missing a convenient and fast method to create new post.
Especially shorter once, like replies to post.</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/week-2022-35/</guid>
      <title>Weekly 2022-35</title>
      <link>https://blog.libove.org/posts/week-2022-35/</link>
      <pubDate>Wed, 07 Sep 2022 07:35:59 +0200</pubDate>
      <description><![CDATA[<ul>
<li><em>Reading list:</em>
<ul>
<li><a href="https://aella.substack.com/p/our-collective-synesthesia-in-graphs">https://aella.substack.com/p/our-collective-synesthesia-in-graphs</a></li>
</ul>
</li>
<li>Blog: Added webmentions to posts. Any (approved) webmention will be listed below posts. Sadly I don't have any yet.</li>
<li><a href="https://npmdrinkinggame.party/">https://npmdrinkinggame.party/</a></li>
</ul>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/week-2022-34/</guid>
      <title>Weekly 2022-34</title>
      <link>https://blog.libove.org/posts/week-2022-34/</link>
      <pubDate>Wed, 31 Aug 2022 07:35:59 +0200</pubDate>
      <description><![CDATA[<ul>
<li><em>Reading list</em>:
<ul>
<li><a href="https://notes.crmarsh.com/using-mypy-in-production-at-spring">https://notes.crmarsh.com/using-mypy-in-production-at-spring</a></li>
<li><a href="https://crimethinc.com/2022/08/22/russia-the-anarcho-communist-combat-organization-an-interview-with-a-clandestine-anarchist-group">https://crimethinc.com/2022/08/22/russia-the-anarcho-communist-combat-organization-an-interview-with-a-clandestine-anarchist-group</a></li>
</ul>
</li>
<li>Django: used <code>only()</code> and <code>defer()</code> to optimise performance
<ul>
<li><code>only</code> has issues with related objects and <code>prefetch_related</code>
<ul>
<li><code>User.objects.all().prefetch_related(&quot;post_set&quot;).only(&quot;name&quot;, &quot;post_set&quot;)</code> will result in an error. Use <code>defer</code> instead to exclude data you don't need.</li>
</ul>
</li>
<li>Idea: Build function that automatically adds <code>only</code> or <code>defer</code> to a queryset based on the Serializer</li>
</ul>
</li>
<li>There will be an atomic bomb event in data gathering. Similar to how atom bombs polluted carbon dating, the spread of AI generated content will pollute future datasets. <a href="https://news.ycombinator.com/item?id=32578142">https://news.ycombinator.com/item?id=32578142</a></li>
<li>Added experimental webmention endpoint to blog.</li>
</ul>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/javascript-list-of-object-to-csv/</guid>
      <title>How To Convert a List of Objects to CSV in JavaScript</title>
      <link>https://blog.libove.org/posts/javascript-list-of-object-to-csv/</link>
      <pubDate>Thu, 25 Aug 2022 19:06:17 +0000</pubDate>
      <description><![CDATA[<p>This is a pretty common task in frontend; you already have some data (retrieved from an API) and want to offer the same data as a CSV.</p>
<p>The problem: most of the top answers you find on Google and StackOverflow are wrong.
They will break in the presence of <code>,</code> or <code>&quot;</code> in the data.
Additionally all I found are vulnerable to <a href="https://owasp.org/www-community/attacks/CSV_Injection">CSV injections</a>.</p>
<h3>Usage:</h3>
<ul>
<li><code>yourData</code> is the data you want to convert to CSV.</li>
<li>In <code>data</code> you have to change two things
<ul>
<li>Change the array to list all headers you want to be included in your CSV</li>
<li>Change the value lookups in the body to match the headers.</li>
</ul>
</li>
<li><code>csvStr</code> will be the finished CSV content. You can use this to construct a download for the user.</li>
</ul>
<h3>Snippet:</h3>
<pre><code class="language-js">const yourData = [
	{value_a: 12, value_b: &quot;some data&quot;},
	{value_a: null, value_b: &quot;no data&quot;},
]

const escape = (str) =&gt; {
	// handle empty cell
	if(str == null || str == '') return '&quot;&quot;'
	// ensure string
	str = `${str}`
	// prevent CSV Injection https://owasp.org/www-community/attacks/CSV_Injection
	const forbidden = new Set([&quot;=&quot;, &quot;+&quot;, &quot;-&quot;, &quot;@&quot;, &quot;\t&quot;, &quot;\n&quot;, &quot;\r&quot;])
	if (forbidden.has(str[0])) {
		str = `'${str}`
	}
	// escape double quotes
	str = str.replace(/&quot;/g, '&quot;&quot;')
	return `&quot;${str}&quot;`
};

const data = [
	// header
	[
		&quot;Header A&quot;,
		&quot;Header B&quot;,
	].map(escape),
	// body
	...yourData.map(
		row =&gt; [
			row[&quot;value_a&quot;],
			row[&quot;value_b&quot;],
		].map(escape)
	)
]
const csvStr = data.map(x =&gt; x.join(&quot;,&quot;)).join(&quot;\n&quot;)

console.log(csvStr);
</code></pre>
<h2>What is CSV</h2>
<p>The naive assumption about CSV (comma separated values) is just appending data with commas as separators in between.
This works in some special cases, but for general data this scheme will quickly break.
When you have data which includes commas it will break your CSV.</p>
<pre><code class="language-js">In  [1, 2, 'hello, world']
CSV 1,2,hello, world
Out [1, 2, 'hello', ' world']
</code></pre>
<p>To avoid this quotes are used. Either all or only the values where it is needed are put into quotes.
The reader will ignore all commas if they are inside quotes.
This way we can represent data that includes commas.</p>
<pre><code class="language-js">In  [1, 2, 'hello, world']
CSV &quot;1&quot;,&quot;2&quot;,&quot;3&quot;,&quot;hello, world&quot;
Out [1, 2, 'hello, world']
</code></pre>
<p>But this only shifts the problem.
What happens if our quote character is used in our data?</p>
<pre><code class="language-js">In  [1, 2, 'hello&quot;, John,&quot; what?']
CSV &quot;1&quot;,&quot;2&quot;,&quot;3&quot;,&quot;hello&quot;, John,&quot; what?&quot;
Out [1, 2, 'hello', ' John', ' what?']
</code></pre>
<p>To avoid this an escape character is used.
The escape character is placed in front of every quote char, which is part of the data.
This indicates to the reader that the following quote is part of the data.
The most common format of CSV uses the quotechar itself as the escapechar.
Every <code>&quot;</code> in the data is simply doubled to <code>&quot;&quot;</code>.</p>
<pre><code class="language-js">In  [1, 2, 'hello&quot;, John&quot;, what?']
CSV &quot;1&quot;,&quot;2&quot;,&quot;3&quot;,&quot;hello&quot;&quot;, John&quot;&quot;, what?&quot;
Out [1, 2, 'hello&quot;, John&quot;, what?']
</code></pre>
<p>The combination of &quot;separator&quot;, &quot;quotechar&quot; and &quot;escapechar&quot; allows us to encode arbitrary data as CSV, without breaking the format of our data.
In principle anything can be chosen for these three characters.
One common variation is to use a semicolon (<code>;</code>) as the separator.
If a tab (<code>\t</code>) is used, the files are called &quot;TSV&quot; (Tab separated values).</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/week-2022-33/</guid>
      <title>Weekly 2022-33</title>
      <link>https://blog.libove.org/posts/week-2022-33/</link>
      <pubDate>Sun, 21 Aug 2022 07:35:59 +0200</pubDate>
      <description><![CDATA[<ul>
<li>Reading List:
<ul>
<li><a href="https://alexanderwales.com/the-ai-art-apocalypse/">The AI Art Apocalypse</a></li>
<li><a href="https://learnmyog.com/">Make Your Own Outdoor Gear</a></li>
</ul>
</li>
<li><em>TIL</em>: Docker logs can get arbitrary big. When using docker-compose, add a logging statement to the service to limit the log file size.</li>
</ul>
<pre><code class="language-yaml">  app:
    logging:
      options:
        max-size: &quot;10m&quot;
        max-file: &quot;3&quot;

</code></pre>
<ul>
<li>Contributions:
<ul>
<li>Pull Request on rsyslog documentation got accepted: <a href="https://github.com/rsyslog/rsyslog-doc/pull/972">Use option.stdsql instead of option.sql in examples</a></li>
<li>Two pull request on <a href="https://github.com/opawg/user-agents">opawg/user-agents</a> accepted: <a href="https://github.com/opawg/user-agents/pull/117">117</a>, <a href="https://github.com/opawg/user-agents/pull/117">118</a></li>
</ul>
</li>
<li>Created a mascot for <a href="https://git.libove.org/h4kor/owl-blogs">owl-blogs</a> using DALL-E 2
<ul>
<li>Prompt: &quot;Logo for a blogging app with an owl as a mascot&quot;</li>
<li>Tried a few variations of the prompt until I found a logo I liked</li>
<li>Opened the result in Inkscape and traced the mascot</li>
<li><a href="https://git.libove.org/h4kor/owl-blogs/src/commit/0b38ba839d90661b46718bfb2b4d11df44810602/assets/owl.png">Result can be found here</a></li>
</ul>
</li>
<li>RSS requires the <code>pubDate</code> to be specified in <a href="https://datatracker.ietf.org/doc/rfc822/">RFC822</a>.  When using the Go time format <code>time.RFC822</code> , the <a href="https://validator.w3.org/feed/check.cgi">rss validator</a> will complain. Using <code>time.RFC1123Z</code>  (<a href="https://datatracker.ietf.org/doc/rfc1123/">RFC1123</a>  updates RFC822) will yield accepted dates.</li>
<li><a href="https://numpad.io/">Found a notepad notepad with a built in unit calculator</a></li>
<li><a href="https://www.crunchydata.com/developers/tutorials">Postgres in your Browser and Tutorials</a></li>
<li>Swapped the blog from hugo to <a href="https://git.libove.org/h4kor/owl-blogs">my own tool</a> <code>\o/</code></li>
</ul>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/week-2022-32/</guid>
      <title>Weekly 2022-32</title>
      <link>https://blog.libove.org/posts/week-2022-32/</link>
      <pubDate>Mon, 15 Aug 2022 07:35:59 +0200</pubDate>
      <description><![CDATA[<ul>
<li>Reading List:
<ul>
<li><a href="https://freeman.vc/notes/falling-for-kubernetes">Falling for Kubernetes</a></li>
</ul>
</li>
<li>Added almost ~90 trees to <a href="https://www.openstreetmap.org/#map=15/51.6229/8.6368">OpenStreetMap</a>  ( Changesets: <a href="https://www.openstreetmap.org/changeset/124831619">124831619</a> <a href="https://www.openstreetmap.org/changeset/124831885">124831885</a>)</li>
<li>Go and Blogging application update:
<ul>
<li>Finally gave this project a name: <strong>owl-blogs</strong></li>
<li>The code is currently available on a private Gitea instance: <a href="https://git.libove.org/h4kor/owl-blogs">https://git.libove.org/h4kor/owl-blogs</a></li>
<li>The encoding package rocks! I were able to build a simple <a href="http://git.libove.org/h4kor/owl-blogs/src/commit/1b99eaa016e449a0104bd328dfae2e35aab5b8e0/rss.go#L8">RSS-Feed</a> by just defining a <code>struct</code> for rss, channel and items.</li>
</ul>
</li>
</ul>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/week-2022-31/</guid>
      <title>Weekly 2022-31</title>
      <link>https://blog.libove.org/posts/week-2022-31/</link>
      <pubDate>Mon, 08 Aug 2022 07:35:59 +0200</pubDate>
      <description><![CDATA[<ul>
<li>Reading list:
<ul>
<li><a href="https://iximiuz.com/en/posts/kubernetes-vs-virtual-machines/">https://iximiuz.com/en/posts/kubernetes-vs-virtual-machines/</a></li>
<li><a href="https://matt-rickard.com/dont-use-kubernetes-yet">https://matt-rickard.com/dont-use-kubernetes-yet</a></li>
<li><a href="https://thenumb.at/Autodiff/">https://thenumb.at/Autodiff/</a></li>
</ul>
</li>
<li>Update: (social) blogging app (See [[2022-29]]).
<ul>
<li>Still looking for a good name.</li>
<li>Can now render a blog.</li>
<li>Using <a href="https://picocss.com/">PicoCSS</a> as a style basis</li>
<li>Started using <a href="https://github.com/julienschmidt/httprouter">httprouter</a> for my.</li>
<li>Still have to implement media files and some header customization for an MVP. Afterwards I might migrate my blog from hugo to my own tool.</li>
</ul>
</li>
<li>Thoughts on [[Go]]:
<ul>
<li>I really like how unit testing is directly integrated into the language</li>
<li>The tooling (using VSCode) is superb</li>
<li>Still don't know what to think about the error handling pattern.</li>
</ul>
</li>
<li><a href="https://hillelwayne.com/post/python-abc/">Crimes with python's pattern matching</a></li>
<li><a href="https://www.youtube.com/watch?v=Z_p9yYXZuCI">&quot;Time is Up!&quot; - Mark Benecke im EU-Parlament</a> (german)</li>
<li>Got access to Dall-E. The output is amazing, but you really need to learn to describe what you are looking for. It's quiet easy to get a good result for a vague concept, but hard if you are looking for something specific.
<ul>
<li>Tried and failed to create in image of the <a href="https://exomemory.fandom.com/wiki/Oubliette">Oubliette</a> from &quot;The Quantum Thief&quot; books</li>
</ul>
</li>
</ul>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/week-2022-30/</guid>
      <title>Weekly 2022-30</title>
      <link>https://blog.libove.org/posts/week-2022-30/</link>
      <pubDate>Sun, 31 Jul 2022 07:35:59 +0200</pubDate>
      <description><![CDATA[<ul>
<li>Added to reading list: <a href="https://blog.pragmaticengineer.com/how-to-become-a-full-time-creator/">How To become a Full Time Creator</a></li>
<li>Listened to Podcast: <a href="https://stackoverflow.blog/2022/07/26/data-analytics-less-creepy-more-empowering/?cb=1">The Overflow - Data analytics: Less creepy, more empowering</a></li>
<li>Changed Obisidian shortcuts to be the same as VSCode
<ul>
<li><code>Ctrl+Shift+P</code> for Command Palette (instead of <code>Ctrl+P</code>)</li>
<li><code>Ctrl+P</code> for Open File (instead of <code>Ctrl+O</code>)</li>
</ul>
</li>
<li>Watched (listened to): <a href="https://media.ccc.de/v/mch2022-196-signal-you-were-the-chosen-one-#t=71">Signal: you were the chosen one!</a></li>
<li>Django TIL: You can pass a queryset into <code>get_object_or_404</code>:</li>
</ul>
<pre><code class="language-python3">user_comment = get_object_or_404(
    self.user.comments,
    pk=self.kwargs[&quot;comment_pk&quot;],
)
</code></pre>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/week-2022-29/</guid>
      <title>Weekly 2022-29</title>
      <link>https://blog.libove.org/posts/week-2022-29/</link>
      <pubDate>Mon, 25 Jul 2022 07:35:59 +0200</pubDate>
      <description><![CDATA[<ul>
<li>Podcast: <a href="https://www.youtube.com/watch?v=1XGiTDWfdpM">Steve Keen: Marxism, Capitalism, and Economics | Lex Fridman Podcast #303</a></li>
<li>Added a simple feedback form to <a href="https://tools.libove.org/generators/">Random Generator Tool</a> . Have to change the presentation, as most people complain about the content of the table they are currently using.
<ul>
<li>Added a feature to copy a random generator, as most people request more options in the tables they are using.</li>
</ul>
</li>
<li>Read the documentation of <a href="https://seaweedfs.github.io/">SeaweedFS</a>. Hopefully I find some time to play with it a bit.</li>
<li>Fix even small inconsistencies in your lower layers immedieatly. The cost of removing them grows exponentially over time and depth!</li>
<li>Started a project to learn some <a href="https://go.dev">Go</a>: A simple (social) blogging application.</li>
<li>Debugged a TypeScript test suite which fails because it runs out of memory:
<ul>
<li>ts-jest has a memory leak. <a href="https://github.com/kulshekhar/ts-jest/issues/1967">https://github.com/kulshekhar/ts-jest/issues/1967</a></li>
<li>setting <code>isolatedModules: true</code>  reduced the memory consumption, but didn't solve the problem. <a href="https://github.com/kulshekhar/ts-jest/issues/1967#issuecomment-1017510990">https://github.com/kulshekhar/ts-jest/issues/1967#issuecomment-1017510990</a></li>
<li>Used <code>@typescript-eslint/no-floating-promises</code> to find promises which were not awaited.</li>
<li>Running Jest with <code>--logHeapUsage</code> showed that each test suite increased the heap size by ~50 MB
<ul>
<li>full command: <code>node --expose-gc ./node_modules/.bin/jest --runInBand --forceExit --logHeapUsage</code></li>
</ul>
</li>
<li>Using <code>@swc/jest</code> instead of <code>jest-ts</code>
<ul>
<li><a href="https://github.com/kulshekhar/ts-jest/issues/1967#issuecomment-1014657022">https://github.com/kulshekhar/ts-jest/issues/1967#issuecomment-1014657022</a></li>
</ul>
</li>
<li>Trying to force jest to do garbage collection:
<ul>
<li><a href="https://stuart.com/blog/tech/guide-removing-frustration-jest-jenkins/">https://stuart.com/blog/tech/guide-removing-frustration-jest-jenkins/</a></li>
<li><a href="https://dev.to/pustovalov_p/reducing-jest-memory-usage-1ina">https://dev.to/pustovalov_p/reducing-jest-memory-usage-1ina</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="http://www.paulbourke.net/fractals/burnship/">Burning Ship Fractal</a></li>
</ul>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/schneller-veganer-gnocchi-brokkoli-auflauf/</guid>
      <title>Schneller Veganer Gnocchi Brokkoli Auflauf</title>
      <link>https://blog.libove.org/posts/schneller-veganer-gnocchi-brokkoli-auflauf/</link>
      <pubDate>Mon, 27 Jun 2022 20:05:10 +0100</pubDate>
      <description><![CDATA[<small>
    
    Servings: <span class="p-yield">4</span>
    , 

    

    
    Prep Time: <time class="dt-duration" value="40 minutes">
        40 minutes
    </time>
    
</small>
<br> <br>

<h2>Ingredients</h2>

<ul>
    
    <li class="p-ingredient">
        800g Gnocchi
    </li>
    
    <li class="p-ingredient">
        1 Brokkoli
    </li>
    
    <li class="p-ingredient">
        1 Dose Tomatenstück
    </li>
    
    <li class="p-ingredient">
        1 Becher veganes Creme Fraiche
    </li>
    
    <li class="p-ingredient">
        1 Zwiebel
    </li>
    
    <li class="p-ingredient">
        1-2 Knoblauchzehen
    </li>
    
    <li class="p-ingredient">
        Veganer Reibekäs
    </li>
    
    <li class="p-ingredient">
        Olivenö
    </li>
    
    <li class="p-ingredient">
        Oregano
    </li>
    
    <li class="p-ingredient">
        Thymian
    </li>
    
    <li class="p-ingredient">
        Salz und Pfeffer
    </li>
    
    <li class="p-ingredient">
        Optional: Paniermehl und Leinensamen
    </li>
    
</ul>

<h2>Instructions</h2>
<p><a href="/media/gnocchi.jpeg"><img src="/thumbnail/gnocchi.jpeg" alt="Schneller Veganer Gnocchi Brokkoli Auflauf."></a></p>
<p>Ein schneller veganer Auflauf mit Gnocchi und Brokkoli. Kann eingefroren oder backfertig im Kühlschrank gelagert werden, für Tage wenn man keine Lust hat zu kochen.</p>
<ul>
<li>Wasser in Topf zum Kochen bringen und Gnocchi nach Packung kochen</li>
<li>Brokkoli in mundgerechte Stücke schneiden</li>
<li>Zwiebel und Knoblauch hacken</li>
<li>Zwiebel in Öl anbraten bis sie anfangen Farbe zu bekommen</li>
<li>Brokkoli und Knoblauch hinzufügen und kurz mit anbraten</li>
<li>Mit Tomaten ablöschen und Dose mit etwas Wasser ausspülen und mit hinzugeben</li>
<li>Für ein paar Minuten aufkochen, dann Creme Fraiche hinzufügen</li>
<li>Nach Geschmack würzen</li>
<li>Fertige Gnocchis in die Pfanne geben und alles gut vermischen</li>
<li>In Auflaufform geben und mit Käse bedecken</li>
<li>Optional etwas Paniermehl und Leinensamen über den Käse sträuen, für einer knusprigere Kruste</li>
<li>für ca. 20 Minuten bei 200°C backen</li>
</ul>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/how-to-install-an-app-image/</guid>
      <title>How to install an AppImage</title>
      <link>https://blog.libove.org/posts/how-to-install-an-app-image/</link>
      <pubDate>Tue, 08 Feb 2022 21:06:10 +0100</pubDate>
      <description><![CDATA[<p>In general <a href="https://appimage.org/">AppImages</a> can just started by <a href="https://docs.appimage.org/user-guide/faq.html#question-how-do-i-run-an-appimage">double clicking</a> or executing them from the terminal (<code>./MyApp.AppImage</code>).
If you want to have them accessible from the start menu, you have to do a little bit more work.</p>
<p>First, move your AppImage to a suitable location.
One option is to create a new directory for all your apps (e.g. an &quot;Applications&quot; directory in your home folder).
I prefer to store them in <code>.local/bin</code>, as this folder is added to the $PATH variable. This allows you to use the app anywhere by calling it in a terminal.
For GUI apps you most likely want to start them from the start menu of your desktop environment. For most apps, this can be achieved by following these steps.</p>
<ol>
<li>Open the App</li>
<li>In a terminal call <code>mount</code> and look for the application name. You can use <code>grep</code> to filter the output. (<code>mount | grep MyApp</code>)</li>
<li>Open the location in your file manager. In the directory you should find:
<ul>
<li>an icon. This can be a PNG in the main folder. In my case (<a href="https://obsidian.md/">Obsidian</a>) the icons where stored in <code>usr/share/icons/hicolor</code></li>
<li>a &quot;desktop configuration file&quot;. It's content should look similar to this:</li>
</ul>
<pre><code>[Desktop Entry]
Name=Obsidian
Exec=AppRun --no-sandbox %U
Terminal=false
Type=Application
Icon=obsidian
StartupWMClass=obsidian
X-AppImage-Version=0.13.19
Comment=Obsidian
MimeType=x-scheme-handler/obsidian;
Categories=Office;
</code></pre>
</li>
<li>Copy the icon into <code>.local/share/icons</code>. If you find a hicolor folder in the app directory, copy the entire folder over. (use <em><strong>Ctrl+H</strong></em> to show hidden files)</li>
<li>Copy the desktop configuration file into the <code>.local/share/applications/</code> directory in your home folder.</li>
<li>Edit the copied desktop file.
<ul>
<li>Replace the app name in the <code>Exec=</code> (and <code>TryExec=</code>) statement with the full path to the AppImage. In the example file above: <code>Exec=~/.local/bin/MyApp</code></li>
</ul>
</li>
</ol>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/rordle-wordle-clone/</guid>
      <title>Rordle - A Wordle Clone</title>
      <link>https://blog.libove.org/posts/rordle-wordle-clone/</link>
      <pubDate>Sun, 16 Jan 2022 12:02:38 +0100</pubDate>
      <description><![CDATA[<p>I've created a clone of <a href="https://www.powerlanguage.co.uk/wordle/">Wordle</a> for the terminal, written in Rust.</p>
<p><strong>Source Code:</strong> <a href="https://github.com/H4kor/rordle">https://github.com/H4kor/rordle</a></p>
<p><strong>Crate:</strong> <a href="https://crates.io/crates/rordle">https://crates.io/crates/rordle</a></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/fun-facts-game/</guid>
      <title>Fun Facts Game</title>
      <link>https://blog.libove.org/posts/fun-facts-game/</link>
      <pubDate>Thu, 13 Jan 2022 21:12:09 +0100</pubDate>
      <description><![CDATA[<p>I've create a small game: <a href="https://fun-facts.rerere.org/">fun-facts.rerere.org</a></p>
<p>How To Play:</p>
<ul>
<li>Meet up with friends, co-workers or people you would like to know better in a video call (or in person once the pandemic ends).</li>
<li>Create a new game and share the link with all players.</li>
<li>At the top of the Page you can enter new fun facts about you, for example: “As a child I owned 10 chickens”.</li>
<li>Wait until some facts are in the pool. The number of facts is shown in the sidebar.</li>
<li>With “Next Facts” you can show change the fact shown to all players. Discuss in the group from whom the fact might be.</li>
<li>You can set your choice in the sidebar by clicking on a name.</li>
<li>Once everybody has set their choice, the submitter (or game creator) can reveal the author of the fact.</li>
<li>Talk about the fun fact.</li>
<li>Repeat until no more facts are available.</li>
</ul>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/are-crypto-currencies-to-blame-for-high-gpu-prices/</guid>
      <title>Are Crypto Currencies to Blame for High GPU Prices?</title>
      <link>https://blog.libove.org/posts/are-crypto-currencies-to-blame-for-high-gpu-prices/</link>
      <pubDate>Thu, 30 Dec 2021 21:48:18 +0100</pubDate>
      <description><![CDATA[<p>Lately I'm paying more attention to the crypto currency and &quot;DeFi&quot; spaces, again.
One discussion that comes up regularly: are miners to blame for the high GPU prices?
To add some facts to this discussion, I’ve done a small analysis of GPU and crypto currency prices to determine if they are related.</p>
<h2>Data Sources</h2>
<h3>GPU Prices</h3>
<p>For GPU prices I used the german website <a href="https://geizhals.de/?cat=gra16_512&amp;v=e&amp;hloc=at&amp;hloc=de&amp;sort=p&amp;bl1_id=1000&amp;xf=9816_03+05+16+-+RTX+3080">https://geizhals.de</a>.
The website shows the prices of items over a time frame of six months.
The data of the plots is delivered as JSON and can be easily scraped.
I scraped the pricing data for the four current-gen NVIDIA graphics cards (RTX 3060 - 3090).
As multiple manufacturers sell the same graphics card chips, I averaged the prices per chip.</p>
<p><a href="/media/gpu-prices.png"><img src="/thumbnail/gpu-prices.png" alt="Plot with prices of graphics cards over time."></a></p>
<h3>Crypto Currency Prices</h3>
<p>I used <a href="https://finance.yahoo.com/quote/BTC-EUR/history?period1=1510358400&amp;period2=1640822400&amp;interval=1d&amp;filter=history&amp;frequency=1d&amp;includeAdjustedClose=true">Yahoo! Finance</a> as my source for the crypto currency prices. You can download the historic data as CSV.</p>
<h2>Evaluation</h2>
<p>For a first impression, I’ve added the bitcoin price to the plot of GPU prices.
To normalize the prices, I divided each time series value by the first price observed.
Squinting at this plot, you can already see that the overall trend in prices roughly line up.</p>
<p><a href="/media/gpu-btc-prices.png"><img src="/thumbnail/gpu-btc-prices.png" alt="Plot with prices of graphics cards and bitcoin over time."></a></p>
<p>For a more accurate analysis, I looked at the correlation factors and created corresponding scatter plots.
Each dot in the plots represents one day.
The Y-axis shows the price of the GPU at that day and the X-axis the price of the crypto currency.
If prices correlated perfectly, all dots would lie on a diagonal line.</p>
<p>For the RTX 3060, there is only a weak correlation between prices.
The correlation between RTX 3060 and ETH prices is not significant and has to be discarded.
Keep in mind that the 3060 was just released on 26 of February, so we have less pricing data compared to the other GPUs.
For the other cards, we see higher correlation factors.
Whenever the price of one rose, the other rose too.
The effect is strongest for the RTX 3090 and is visible in Bitcoin and Ethereum.</p>
<p><a href="/media/evaluation.png"><img src="/thumbnail/evaluation.png" alt="Scatter plots showing the relationship between crypto currency prices and GPU prices."></a></p>
<p>This analysis still has a flaw, as it does not correct for the overall trend in the price development.
Prices change over time because of factors affecting all products.
For example, inflation will rise all prices over longer observations.
This creates a false correlation if you compare prices over a long time frame.
To correct for this, the overall trend has to be removed from the data.
I used a linear regression to remove the trend.</p>
<p><a href="/media/gpu-btc-prices-detrend.png"><img src="/thumbnail/gpu-btc-prices-detrend.png" alt="Plot with prices of graphics cards and bitcoin over time. The trend is removed."></a></p>
<p>With the trend removed, the measured correlation between the prices is weaker, as expected, as we removed a linear factor from the data.
The data still shows a correlation between crypto currency and GPU prices.
The correlation is stronger for Bitcoin than for Ethereum.</p>
<p><a href="/media/evaluation-detrend.png"><img src="/thumbnail/evaluation-detrend.png" alt="Scatter plots showing the relationship between crypto currency prices and GPU prices. The trend is removed."></a></p>
<p>To test the validity of these correlations, I did the same analysis for CPU from AMD and Intel.
I used the prices of the generations; Ryzen 5000 and i-11000.
The AMD prices show no correlation with the crypto currency prices (p-value below 0.05).
For Intel chips, we can observe a slight correlation.
This gives me more confidence that the correlation is real for GPUs.</p>
<p><a href="/media/evaluation-cpu.png"><img src="/thumbnail/evaluation-cpu.png" alt="Scatter plots showing the relationship between crypto currency prices and CPU prices. The trend is removed."></a></p>
<h2>Conclusion</h2>
<p>We can see a correlation between GPU prices and the value of crypto currencies, but correlation does not mean causation.
A causation is plausible in this case.
Miners earn a lot of money with their businesses and can predict how much they will earn on average with each graphics card.
As long as their earnings are high enough, it is profitable for miners to buy cards at higher prices.
Bitcoin miners rarely use GPUs anymore, but ASICs.
The production of ASICs may add more price pressure on silicon and chip production.
I don't have any data on the amount of production capacity being used by ASICs, so I don't know how much pressure they produce.</p>
<p>Why is this a problem? There is nothing inherent to blockchains that demands such a high dependence on computing hardware.
The resources spent by miners only serves as a mechanism to increase the cost of changing data in the blockchain.
This cost serves as a mechanism to secure blockchain.
Other mechanism for securing blockchains exist and successfully used (see Proof of Stake).
Proof of Work creates external costs (environmental impact, higher electricity prices, GPU scarcity) to benefit a small group of miners.</p>
<p><a href="/media/GPUPricing.zip">Download the raw data and notebook.</a></p>
<p><span class="hashtag"><a class="p-category" href="/tags/cryptocurrency/">#cryptocurrency</a></span> <span class="hashtag"><a class="p-category" href="/tags/gpu/">#gpu</a></span> <span class="hashtag"><a class="p-category" href="/tags/analysis/">#analysis</a></span></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/nfts-in-games-are-bad-here-is-an-alternative/</guid>
      <title>NFTs in Games Are Bad! Here Is an Alternative.</title>
      <link>https://blog.libove.org/posts/nfts-in-games-are-bad-here-is-an-alternative/</link>
      <pubDate>Mon, 13 Dec 2021 21:39:21 +0100</pubDate>
      <description><![CDATA[<p>NFTs in gaming are just a bad idea.
They create artificial scarcity in order to create a pure manifestation of capitalism in the gaming world.
This is not fun! Games should allow you to escape from reality, not bring the problems of real life into virtual life.</p>
<p>Do gamers want this? Ask any gamer about pay-to-win, or in-game purchases, and you got your answer.</p>
<h2>Alternative Financing</h2>
<p>What are the alternatives? Let’s imagine the manifestation of another ideology: <a href="https://en.wikipedia.org/wiki/From_each_according_to_his_ability,_to_each_according_to_his_needs">Communism</a>.</p>
<p>Everybody pays as much as they can spare to game developers.
The game developers use this money to pay artists and developers, which create the content of the former NFTs.
Every player can use these new skins and items, no matter how much they paid.
There is no special treatment for paying more.
Your sole motivation for paying is liking the games and wanting to see it grow.</p>
<p>This is certainly better than NFTs, but still does not enhance the game.
A game where you cannot progress or achieve anything is boring.
So let’s mix in meritocracy.</p>
<p>We still finance the content as above, however it’s not given to the players directly.
Instead, the items/skins are tied to achievements in the game in a deterministic way.
Want that new sword skin? Win 100 fights in the Arena of Doom.
This gives players a goal and rewards skill in the game.</p>
<p>I think these two principles create a system which most gamers will like.
You don’t have to pay for new skins/items, but you can still brag about your new skin because everyone knows how hard it is to obtain.
This would also be an improvement over the current system of micro transactions and in-game purchases.</p>
<p>Now you may ask: “But what about sharing items between games? That’s the whole reason for using blockchain and NFT.”</p>
<h2>Decentralization and Ownership</h2>
<p>Blockchain will not solve the problem of sharing items between games.</p>
<p>Tracking ownership is the smallest of problems.
The more hard questions are:
How do you translate item stats from one game to the other?
How do you exchange assets in a unified way?
How do you convince large publishers to open up their ecosystems?</p>
<p>So here is my solution: <a href="https://en.wikipedia.org/wiki/Federation_(information_technology)">Federation</a>.</p>
<p>First, we need to find a common way to connect game account with each other.
An established method is <a href="https://oauth.net/2/">OAuth2</a>.
This would allow players to connect accounts of different games.
Once connected, your game can retrieve the list of items a player unlocked in the other game.</p>
<p>Another possibility, and a more decentralized method, is the <a href="https://solidproject.org/">Solid Project</a>, where your data is stored in a place you control.
This would have the added benefit that your game assets still exist when the publisher of the stops maintaining the game.</p>
<p>Additionally to authentication, we need an API to exchange information about the in-game assets between games.
The API needs to present items in a standardized way, which is still flexible enough to cover “all” items in games.
One possibility would be to use <a href="https://en.wikipedia.org/wiki/Resource_Description_Framework">RDF</a> and define a standard vocabulary for game items.
RDF uses three-part structure to describe properties of subjects.
With in-game items, this would be equivalent to: “sword is green”, “sword has +3 strength”.
By defining a standard vocabulary (e.g.
how to represent colors), games could easily translate from the common description to in-game attributes.</p>
<p>You might also want to include assets in the API.
Again, we need heavy standardized representation (e.g.
glTF or obj for 3D assets, PNG for images, ...).
These assets would mostly only serve as references for designers to create their own assets.
Games are art pieces, and it is almost impossible to share assets between them in an automated way.</p>
<p>Once our authentication flows and APIs are defined, we can integrate them into games.
This still requires work from artists and programmers.
The game developers decide which game they want to support for exchanging items with.
Connecting two shooters is easier than connecting a fantasy RPG to a sci-fi strategy game.
Using the assets API the designers change or recreate the assets to be used in their game and adjust the stats to be balanced.
When the players connect their game accounts, the information about ownership is exchanged, and the modified items awarded to the player.</p>
<p>Most games will probably only support game connections with similar settings where the translation is straightforward.
But over time, more and more games will build connections between varying genres.
At this point the true power of a federated system starts.</p>
<p>The connections of games build a graph.
This allows you to support many game connections without explicitly connecting them.
A player earns an item in Game A.
This item is translated to another item in Game B.
If your game supports a connection to Game B, it can automatically supports all items which are also supported by Game B.
This way, your game will automatically accept more and more items from different games without more work for the designers.</p>
<h2>Conclusion</h2>
<p>I think this creates a system that is beneficial to all participants.
New games will use connection to popular, similar games to attract players to their game.
Players that get a bit tired of a game can swap to new games and take (some) of their assets over.
Large publishers might use this system to cross promote their games.</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/winter-wonder-portal/</guid>
      <title>Winter Wonder Portal</title>
      <link>https://blog.libove.org/posts/winter-wonder-portal/</link>
      <pubDate>Sat, 04 Dec 2021 16:46:20 +0100</pubDate>
      <description><![CDATA[<!-- raw html -->
<p><video width="100%" src="/media/xmas.webm" alt="A rotating christmas reaves in a snow landscape. The center of the reave has a portal leading to a mystical building." controls></video></p>
<p><a href="/media/xmas.webm">A rotating christmas reaves in a snow landscape. The center of the reave has a portal leading to a mystical building.</a></p>
<ul>
<li><strong>Music:</strong>  Scott Holmes Music - This is Christmas  CC BY-NC 4.0</li>
<li><strong>Tavern in Portal:</strong> <a href="https://blendswap.com/blend/28534">Medieval House 007 - Blacksmith</a></a>  CC-0</li>
<li><strong>Blend File:</strong> <a href="/media/xmas.blend">xmas.blend</a> (without Tavern)</li>
</ul>
<p><span class="hashtag"><a class="p-category" href="/tags/render/">#render</a></span> <span class="hashtag"><a class="p-category" href="/tags/blender/">#blender</a></span> <span class="hashtag"><a class="p-category" href="/tags/xmas/">#xmas</a></span></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/blender-solar-panel-material/</guid>
      <title>Blender Solar Panel Material</title>
      <link>https://blog.libove.org/posts/blender-solar-panel-material/</link>
      <pubDate>Fri, 08 Oct 2021 21:43:45 +0200</pubDate>
      <description><![CDATA[<p><img src="media/preview.png" alt="Rendering of a solar panel."></p>
<p>This is fully procedurally created solar panel material. Borders, wires and colors can be easily adjusted.</p>
<p>You can use this assets under the <a href="https://creativecommons.org/licenses/by/4.0/">CC BY 4.0 License</a>.</p>
<p><a href="/media/SolarPanel.blend">Solar Panel Material</a></p>
<p><span class="hashtag"><a class="p-category" href="/tags/blender/">#blender</a></span> <span class="hashtag"><a class="p-category" href="/tags/texture/">#texture</a></span> <span class="hashtag"><a class="p-category" href="/tags/solar/">#solar</a></span></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/setup-rabbitmq-with-users-for-docker-compose/</guid>
      <title>Setup Rabbitmq With Users for Docker Compose</title>
      <link>https://blog.libove.org/posts/setup-rabbitmq-with-users-for-docker-compose/</link>
      <pubDate>Fri, 30 Jul 2021 21:15:22 +0200</pubDate>
      <description><![CDATA[<p>This guide is written for <strong>RabbitMQ 3.9</strong>!</p>
<h1>Configuring the RabbitMQ Server</h1>
<p>For the configuration of the RabbitMQ server we will use a <code>rabbitmq.conf</code> and a <code>definitions.json</code>. I stored these files in a <code>rabbitmq</code> folder to keep my project folder structure clean. This setup is derived from  <a href="https://stackoverflow.com/a/42251266/1224467"><strong>sudo</strong>s answer on StackOverflow</a></p>
<p>The <code>rabbitmq.conf</code> deactivates the default <em>guest</em> user and tells RabbitMQ to load the definition file.</p>
<pre><code>loopback_users.guest = false
management.load_definitions = /etc/rabbitmq/definitions.json
</code></pre>
<p>In the definition file we can define our users, vhosts and permissions.</p>
<pre><code class="language-json">{
 &quot;rabbit_version&quot;: &quot;3.9&quot;,
 &quot;users&quot;: [
  {
   &quot;name&quot;: &quot;local_jobs&quot;,
   &quot;password_hash&quot;: &quot;&gt;&gt;&gt;HASH&lt;&lt;&lt;&quot;,
   &quot;hashing_algorithm&quot;: &quot;rabbit_password_hashing_sha256&quot;,
   &quot;tags&quot;: &quot;&quot;
  },
  {
   &quot;name&quot;: &quot;adminuser&quot;,
   &quot;password_hash&quot;: &quot;&gt;&gt;&gt;HASH&lt;&lt;&lt;&quot;,
   &quot;hashing_algorithm&quot;: &quot;rabbit_password_hashing_sha256&quot;,
   &quot;tags&quot;: &quot;administrator&quot;
  }
 ],
 &quot;vhosts&quot;: [
  {
   &quot;name&quot;: &quot;\/&quot;
  },
 ],
 &quot;permissions&quot;: [
  {
   &quot;user&quot;: &quot;local_jobs&quot;,
   &quot;vhost&quot;: &quot;\/&quot;,
   &quot;configure&quot;: &quot;.*&quot;,
   &quot;write&quot;: &quot;.*&quot;,
   &quot;read&quot;: &quot;.*&quot;
  }
 ],
 &quot;parameters&quot;: [],
 &quot;policies&quot;: [],
 &quot;queues&quot;: [],
 &quot;exchanges&quot;: [],
 &quot;bindings&quot;: []
}
</code></pre>
<p>To add the configurations to the RabbitMQ server they are added via the volumes options in the <code>docker-compose.yml</code></p>
<pre><code class="language-yaml">  rabbitmq:
    hostname: rabbitmq
    image: rabbitmq:3.9-management
    command: rabbitmq-server
    ports:
      - &quot;5672:5672&quot;
      - &quot;15672:15672&quot;
    volumes:
      - ./rabbitmq/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf:ro
      - ./rabbitmq/definitions.json:/etc/rabbitmq/definitions.json:ro
</code></pre>
<h1>Generating Password Hashs</h1>
<p>In order to generate the password hashs I used the python script by <a href="https://stackoverflow.com/a/53016240/1224467"><strong>Todd Lyons</strong> on StackOverflow</a></p>
<pre><code class="language-python">#!/usr/bin/env python3

# rabbitMQ password hashing algo as laid out in:
# http://lists.rabbitmq.com/pipermail/rabbitmq-discuss/2011-May/012765.html

from __future__ import print_function
import base64
import os
import hashlib
import struct
import sys

# This is the password we wish to encode
password = sys.argv[1]

# 1.Generate a random 32 bit salt:
# This will generate 32 bits of random data:
salt = os.urandom(4)

# 2.Concatenate that with the UTF-8 representation of the plaintext password
tmp0 = salt + password.encode('utf-8')

# 3. Take the SHA256 hash and get the bytes back
tmp1 = hashlib.sha256(tmp0).digest()

# 4. Concatenate the salt again:
salted_hash = salt + tmp1

# 5. convert to base64 encoding:
pass_hash = base64.b64encode(salted_hash)

print(pass_hash.decode(&quot;utf-8&quot;))
</code></pre>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/wahlprogramme/</guid>
      <title>Wahlprogramme</title>
      <link>https://blog.libove.org/posts/wahlprogramme/</link>
      <pubDate>Sat, 15 May 2021 21:26:32 +0200</pubDate>
      <description><![CDATA[<p>Ich habe ein kleines <a href="https://wahlprogramme.rerere.org">Tool für die Bundestagswahl 2021</a> gebaut um nachzugucken wie oft Begriffe in den Wahlprogrammen der Parteien vorkommen.</p>
<p>Die Suche ermöglicht es mehrere Begriffe in Relation zueinander darzustellen.</p>
<ul>
<li>Begriffe werden durch Kommas getrennt (z.B. <a href="https://wahlprogramme.rerere.org/year/2021?query=Klima%2CWirtschaft"><code>Klima,Wirtschaft</code></a>)</li>
<li>Begriffe können durch Pluszeichen addiert werden (z.B. <a href="https://wahlprogramme.rerere.org/year/2021?query=Klima%2BWirtschaft"><code>Klima+Umwelt</code></a>)</li>
<li>Um nach einem exakte Begriff zu suchen kann dieser in Anführungszeichen gesetzt werden (z.B. <a href="https://wahlprogramme.rerere.org/year/2021?query=%22Klima%22"><code>&quot;Klima&quot;</code></a>)</li>
</ul>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/bright-future/</guid>
      <title>Bitcoin, for a brighter future!</title>
      <link>https://blog.libove.org/posts/bright-future/</link>
      <pubDate>Sat, 17 Apr 2021 16:01:45 +0200</pubDate>
      <description><![CDATA[<p><img src="media/bitcoin_3.png" alt="Bitcoin, for a brighter future!"></p>
<p>Contains:</p>
<ul>
<li><a href="https://skfb.ly/6QWrS">Tesla Cybertruck</a> by PolyDucky is licensed under <a href="http://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution</a>.</li>
</ul>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/the-cost-of-proof-of-work/</guid>
      <title>The Cost of Proof of Work</title>
      <link>https://blog.libove.org/posts/the-cost-of-proof-of-work/</link>
      <pubDate>Tue, 23 Mar 2021 17:06:05 +0100</pubDate>
      <description><![CDATA[<h1>Introduction</h1>
<p>In the last months I've spent a lot of my time researching and thinking about the ecological impact of blockchain.
<a href="https://cbeci.org/">Bitcoin</a> and <a href="https://digiconomist.net/ethereum-energy-consumption">Ethereum</a>, the two most popular blockchain technologies, have a vast power consumption.
The power consumption is not inherent to blockchain technologies, but a result of the way these blockchains are secured.</p>
<p>The network of computers running Bitcoin and Ethereum establish trust by making changes to the blockchain expensive.
This is achieved by requiring to solve a puzzle whenever you want to append something to the chain.
Miners (people who solve these puzzles) have to spend money on hardware and electricity to solve the puzzles.
The difficulty of the puzzle is adjusted automatically with the number (and power) of computers trying to solve the puzzle.
Without adjusting the difficulty, changes would become inexpensive and the blockchain would become vulnerable.
This method of securing a blockchain is called &quot;<a href="https://en.wikipedia.org/wiki/Proof_of_work">Proof of Work</a>&quot; (PoW).</p>
<p>There are other methods to establish trust in a blockchain network, like <a href="https://en.wikipedia.org/wiki/Proof_of_stake">Proof of Stake</a> (PoS), which require far less energy.
I want to focus on Proof of Work as this is the system which currently secures the most value in the crypto currency space.</p>
<p>It's hard to calculate the real power consumption of a blockchain.
Estimates are mostly concerned with the current hash rate (how many tries to solve the puzzle are executed each second) and the efficiency of the used hardware.
It is almost impossible to know how efficient the hardware used is which leads to large ranges in the estimation.
As of writing Bitcoins energy usage probably lies somewhere between <a href="https://cbeci.org/">43 TWh and 477 TWh per year</a>.
In the following I will not concern myself with the efficiency of the hardware.
Instead I try to derive the energy consumption of any blockchain (using PoW) from the value of the coins and how much miners are paid in block rewards.</p>
<h1>Why miners mine</h1>
<p>The reason why miners do their job of building and operating mining pools is simple: <em>they want to earn money</em>.
For each block that is found by a miner she earns some coins.</p>
<p><strong>There are generally two ways miners are rewarded for their work:</strong></p>
<p>First the miner can create some new coins for each found block.
The block rewards can change over time, for example bitcoin started with 50 BTC per block and the reward is lowered gradually until, eventually, no new coins are added to the blockchain.
This mechanism is used to create the initial pool of coins and to incentivise mining while very little transaction fees are available.</p>
<p>When the block reward is diminished over time transaction fees play an important role to incentivise mining.
When you create a transaction to be added to the blockchain a transaction fee can be added.
This fee is given to the miner who created the block containing the transaction.
Once no more coins are generated out of thin air the transaction fees have to pay the bills of the miners.</p>
<p>The block reward gives us an upper limit of the expenses of all miners.
If 10 BTC are earned on average per block, all miners combined can at most spent 10 BTC to create a new block.
If more resources were spent on creating a block over a longer period of time miners would go bankrupt.</p>
<h1>Why efficiency does not matter</h1>
<p>Miners have two main costs: hardware and electricity, everything else is overhead.
In order to be profitable they constantly try to minimize the price per computed hash.
The number of blocks generated by a miner is directly proportional to the percent of the global hash rate the miner controls.
A miner holding 1% of the global hash rate will be able to create 1% of the blocks.
As hardware gets more efficient, in terms of hashes per energy usage, miners will upgrade their hardware to minimize their cost and to secure more blocks.
When other miners upgrade their hardware too the advantage of a single miner is diminished as the improved hash rate is lost when everybody increases their hash rate.
This leads to a cycle of constant upgrade to stay ahead of the curve.
The upgrade cycle only leads to a higher global hash rate but does not lower the power consumption of the networks as a whole as miners only try to secure a bigger piece of the block reward cake.</p>
<p>If you look at mining from above, ignoring the single miner, you see a collective that constantly buys more efficient hardware but somehow manages not to reduce its power consumption.
Instead the power consumption of the collective is dictated by the rewards themself.</p>
<p>Higher rewards for mining, either by coins becoming more valuable or more fees being paid, lead to investments into mining equipment.
Existing miners buy more machines and new people start mining.
This leads to a higher energy consumption of the mining collective.</p>
<p>Any mining reward not used for new hardware (or kept as profit) will be used to buy energy to run the machines.</p>
<h1>How much energy will be used</h1>
<p>This gives us four factors that control the energy usage of all miners.</p>
<ul>
<li><strong>Block Reward:</strong> How much money can be earned by creating a block (Coin/Block)</li>
<li><strong>Exchange Rate:</strong> What is the value of a coin ($/Coin)</li>
<li><strong>Proportional Energy Costs:</strong> Which portion of the mining rewards is used for electricity (%)</li>
<li><strong>Electricity Price:</strong> How much does an average miner pay for a kilowatt-hour ($/kWh)</li>
</ul>
<p>The used energy of the network per block can be computed as:</p>
<pre><code>([Block Reward] * [Exchange Rate] * [Proportional Energy Costs]) / [Energy Price]
 = [Energy Usage per Block]
</code></pre>
<p>Abstracting away blocks and exchange rates this could be simplified to:</p>
<pre><code>a * [Daily Rewards] / [Energy Price] = [Daily Energy Usage]
</code></pre>
<p>where <code>a</code> is the proportion of the rewards used for energy</p>
<h1>Blockchain Energy Usage Calculator</h1>
<p>(The calculation assumes that a new block is generated every 10 minutes)</p>
<style>
    #app  {
        display: table;
        margin: 0 auto;
    }
    #app p     { display: table-row;  }
    #app label { display: table-cell; }
    #app input { display: table-cell; }
    #app strong { display: table-cell; }
</style>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<div id="app">
    <p>
        <label>Block Reward (Coin/Block)</label>
        <input v-model="reward" type="number">
    </p>
    <p>
    <label>Exchange Rate ($/Coin)</label>
    <input v-model="rate" type="number">
    </p>
    <p>
        Revenue per Block:
        <strong>{{revenue}} $</strong>
    </p>
    <p>
    <label>Portion used for Energy (%)</label>
    <input v-model="portion" type="number">
    </p>
    <p>
    <label>Energy Price ($/kWh)</label>
    <input v-model="price" type="number">
    </p>
    <p>
        Energy per Block:
        <strong>{{block}} GWh</strong>
    </p>
    <p>
        Energy per Year:
        <strong>{{year}} TWh</strong>
    </p>
</div>
<script type="text/javascript">
    var app = new Vue({
        el: '#app',
        data: {
            reward: 6.5,
            rate: 50000,
            portion: 50,
            price: 0.05,
        },
        computed: {
            revenue() {
                return this.reward * this.rate
            },
            block () {
                var kwh = (this.reward * this.rate * this.portion/100) / this.price;
                var gwh = kwh / 1000000
                return gwh;
            },
            year () {
                return this.block * 6 * 24 * 365 / 1000;
            }
        }
    })
</script>
<h1>Conclusion</h1>
<p>The energy consumption of a PoW blockchain is proportional to the value a miner can earn per block.
If the value of the reward doubles, either by higher fees or higher coin values, the energy consumption will double eventually.
Similarly the power consumption will rise if the price of energy drops.</p>
<p>A widely used blockchain, which potentially could replace fiat currencies, would have a high transaction amount leading to a large pool of fees to be collected by miners.
This would eventually lead to a corresponding energy consumption.</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/end-of-covid19/</guid>
      <title>End of COVID-19</title>
      <link>https://blog.libove.org/posts/end-of-covid19/</link>
      <pubDate>Fri, 08 Jan 2021 22:56:56 +0100</pubDate>
      <description><![CDATA[<p>I've created a simple website to project and visualize the end of the COVID-19 pandemic.
The page is refreshed once per day.</p>
<p><a href="https://h4kor.github.io/end-of-covid/">https://h4kor.github.io/end-of-covid/</a></p>
<p>You can select countries at the bottom of the page.
Feedback, feature requests or improvements can be submitted on <a href="https://github.com/h4kor/end-of-covid">GitHub</a></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/aachen-bussgelder/</guid>
      <title>Aachen Bussgelder</title>
      <link>https://blog.libove.org/posts/aachen-bussgelder/</link>
      <pubDate>Wed, 30 Dec 2020 20:03:03 +0100</pubDate>
      <description><![CDATA[<p>Ich hab einen Datensatz der schon länger bei mir auf der Platte liegt etwas aufbereitet und veröffentlicht.
Nach einem Vortrag auf der CozyConf über Datasette hab ich diesen Datensatz als Beispiel benutzt um das Tool einmal selbst auszuprobieren.</p>
<p>Das Ergebnis kann hier gefunden werden: <a href="https://aachen-bussgelder-k3fasuseya-ey.a.run.app/">https://aachen-bussgelder-k3fasuseya-ey.a.run.app/</a></p>
<p>(Das Repository: <a href="https://github.com/h4kor/aachen-bussgelder">https://github.com/h4kor/aachen-bussgelder</a>)</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/walls-in-tile-map-with-tile-strings/</guid>
      <title>Walls in Tile Maps with Tile Strings</title>
      <link>https://blog.libove.org/posts/walls-in-tile-map-with-tile-strings/</link>
      <pubDate>Fri, 25 Dec 2020 23:58:35 +0100</pubDate>
      <description><![CDATA[<p><img src="media/title.png" alt="Example of a tile map."></p>
<p>I'm working on a small game with a procedural map. As the whole game is set in a labyrinth-like building I've decided to use tile maps.
The map generation algorithm produces a 2D matrix with <code>0</code>s and <code>1</code>s.
A <code>1</code> indicates that the tile is walkable while a <code>0</code> represents an impassable field.</p>
<p>In a simple 2D setting this could be represented by using a floor sprite for each <code>1</code> and a wall sprite for each <code>0</code>.
However, this isn't very appealing.
Instead the visual representation of a tile should be depended on its neighborhood.</p>
<p>Each tile can have one of 256 <code>(2^8)</code> configurations.
These configurations can be reduced to 41 by considering rotations as one configuration.
As multiple configurations need the same wall layout we will only need 15 tiles to render the tile map.</p>
<p><img src="media/configs.png" alt="Configurations of a tile with its neighbors."></p>
<p>My first approach to implement the rendering was to use shit ton of <code>if</code> statements.
The approach tested for all configurations to find the needed piece and rotation.
A few shortcuts prevented that I had to test for all 256 configurations but it was still too much.
After implementing this approach for 4 tile pieces I gave up.</p>
<p>My next idea was to represent the knowledge of which piece and rotation is required for each configuration in a lookup map.
This approach is similar to <a href="https://en.wikipedia.org/wiki/Marching_cubes">marching cubes</a> where you need a lookup map to know how to construct the faces of a cube.
After writing down 30 entries of the lookup map I came up with the following idea (which I will call &quot;Tile Strings&quot;).</p>
<p><img src="media/tile_string.png" alt="Calculating the Tile String."></p>
<p>Instead of checking for all 256 configurations, we only look at a <code>2x2</code> section of the neighborhood.</p>
<p>We start with the <code>2x2</code> patch in the upper left corner and determine the character it represents.
This character represents whether the left side of the tile needs a <strong>wall</strong>, a <strong>corner</strong> or needs to be <strong>empty</strong>.</p>
<p>The <code>2x2</code> patch is than <em>rotated</em> around the center to determine the characters for the other sides.
After the full rotation we have a 4 character string representing the configuration of the center tile, for example <code>&quot;WCCE&quot;</code>.
This is our tile string.</p>
<p><em>Example code for a single <code>2x2</code> check (written in <a href="https://docs.godotengine.org/en/stable/getting_started/scripting/gdscript/gdscript_basics.html">GDScript</a>):</em></p>
<pre><code class="language-python">if cell(p + Vector3(-1, 0, 0)):
    if cell(p + Vector3(0, 0, -1)) and not cell(p + Vector3(-1, 0, -1)):
        tile_str += &quot;C&quot;
    else:
        tile_str += &quot;E&quot;
else:
    tile_str += &quot;W&quot;
</code></pre>
<p>With the calculated tile string we can start to search for a suitable sprite or model in a library (our library should consist of 15 assets named by their tile string).
We still have to manipulate the tile string and determine the rotation, as we most likely only want to create 15 assets.</p>
<p>As the tile string represents a unique configuration we only have to rotate it (at most 3 times) to find a fitting configuration in our library.
We start by checking if the tile string already fits an element in our library. If this is the case we can just use it!
If no element fits we <em>rotate</em> by 90 degrees, by moving the last character of the string to the front, <code>&quot;WCCE&quot;</code> is transformed to <code>&quot;EWCC&quot;</code>.
The number of rotations are counted.
We do this at most 3 times.
If you didn't find a fitting element after 3 rotations your library is not yet complete.
Once the rotated element fits, it can be inserted into our scene with the determined rotation.</p>
<p><em>Example code for finding an element in the library (written in <a href="https://docs.godotengine.org/en/stable/getting_started/scripting/gdscript/gdscript_basics.html">GDScript</a>):</em></p>
<pre><code class="language-python">var rotation = 0
for i in range(4):
    if tile_str in library:
        add_part(position, rotation, tile_str)
        break
    rotation += 90
    cfg_str = cfg_str[3] + cfg_str[0] + cfg_str[1] + cfg_str[2]
</code></pre>
<p>Your library does not have to be limited to 15 elements.
Maybe corridors running north-south should have a different design than east-west corridors.
Just add a new element with the corresponding tile string to your library.</p>
<p><img src="media/result.png" alt="Rending of room using the tile string algorithm."></p>
<p>This approach only requires a few lines of code.
There is probably a lot of potential for improvement but it works perfectly for my use case.
The process of developing this once again showed me that it is worthwhile to take a step back and think about your problem.
The first two approaches showed me properties of the problem and implementing them (partially) deepened my knowledge, which ultimately led to the &quot;Tile String&quot; algorithm.</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/veganer-flammkuchen/</guid>
      <title>Veganer Flammkuchen</title>
      <link>https://blog.libove.org/posts/veganer-flammkuchen/</link>
      <pubDate>Sat, 19 Dec 2020 17:05:10 +0100</pubDate>
      <description><![CDATA[<small>
    
    Servings: <span class="p-yield">2</span>
    , 

    

    
    Prep Time: <time class="dt-duration" value="40 minutes">
        40 minutes
    </time>
    
</small>
<br> <br>

<h2>Ingredients</h2>

<ul>
    
    <li class="p-ingredient">
        175 g 550 Mehl
    </li>
    
    <li class="p-ingredient">
        100 ml lauwarmes Wasser
    </li>
    
    <li class="p-ingredient">
        Prise Salz
    </li>
    
    <li class="p-ingredient">
        5 g Hefe
    </li>
    
    <li class="p-ingredient">
        Vegane Crème fraîe
    </li>
    
    <li class="p-ingredient">
        Rauchsalz
    </li>
    
    <li class="p-ingredient">
        Knoblauchpulver
    </li>
    
    <li class="p-ingredient">
        Pfeffer
    </li>
    
    <li class="p-ingredient">
        1 rote Zwiebel
    </li>
    
    <li class="p-ingredient">
        6 Champignons
    </li>
    
</ul>

<h2>Instructions</h2>
<p><a href="/media/flammkuchen.jpg"><img src="/thumbnail/flammkuchen.jpg" alt="Veganer Flammkuchen."></a></p>
<ul>
<li>Teigzutaten zu einem glatten Teig kneten</li>
<li>Teig im Kühlschrank mindestens 24 Stunden gehen lassen. Alle 24 Stunden Teig durch kurzes kneten entlüften.</li>
<li>30-60 Minuten vor dem backen zwei Teigbälle a 100-150 g formen und warm gehen lassen und Backofen auf 300°C vorheizen</li>
<li>Teig sanft zu möglichste dünnen Fladen formen</li>
<li>Fladen mit Crème fraîche bestreichen und mit nach belieben würzen</li>
<li>Zwiebeln in dünne Ringe schneiden und Champignons würfeln und Fladen belegen</li>
<li>ca 5 Minuten bei 300°C backen</li>
</ul>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/COVID_Tote/</guid>
      <title>COVID-19 Tote in Relation setzen</title>
      <link>https://blog.libove.org/posts/COVID_Tote/</link>
      <pubDate>Sat, 19 Dec 2020 17:05:10 +0100</pubDate>
      <description><![CDATA[<p>Momentan sterben täglich etwa 800 Menschen an COVID-19.
Eine Zahl unter der ich mir nichts vorstellen kann weil mir der Vergleich fehlt.
Was bedeutet es wenn ~800 Menschen pro Tag sterben?</p>
<pre><code class="language-python">einwohner = 83_000_000
tote_pro_tag = 800
prozent_tote_pro_tag = tote_pro_tag / einwohner
print(f&quot;{prozent_tote_pro_tag * 100:.4f}%\tder Bevölkerung stirbt pro Tag&quot;)
print(f&quot;{prozent_tote_pro_tag * 100 * 7:.4f}%\tder Bevölkerung stirbt pro Woche&quot;)
print(f&quot;{prozent_tote_pro_tag * 100 * 30:.4f}%\tder Bevölkerung stirbt pro Monat&quot;)
print(f&quot;{prozent_tote_pro_tag * 100 * 365:.4f}%\tder Bevölkerung stirbt pro Jahr&quot;)
</code></pre>
<pre><code>0.0010%	der Bevölkerung stirbt pro Tag
0.0067%	der Bevölkerung stirbt pro Woche
0.0289%	der Bevölkerung stirbt pro Monat
0.3518%	der Bevölkerung stirbt pro Jahr
</code></pre>
<p>Das ganze in Prozenten auszudrücken macht es nicht viel besser.
Wie sieht es also aus wenn wir die COVID-19 Sterblichkeit auf andere Situationen übertragen?</p>
<h2>Wie riskant wäre es zu fliegen?</h2>
<pre><code class="language-python">fluggaeste_deutschland = 227_000_000 # Quelle: https://de.statista.com/themen/1060/flugpassagiere/
tote_fluggaeste_pro_tag = prozent_tote_pro_tag * fluggaeste_deutschland
print(f&quot;{int(tote_fluggaeste_pro_tag)} Menschen würden täglich beim fliegen sterben&quot;)
print(f&quot;{int(tote_fluggaeste_pro_tag * 7)} Menschen würden wöchentlich beim fliegen sterben&quot;)
print(f&quot;{int(tote_fluggaeste_pro_tag * 30)} Menschen würden monatlich beim fliegen sterben&quot;)
</code></pre>
<pre><code>2187 Menschen würden täglich beim fliegen sterben
15315 Menschen würden wöchentlich beim fliegen sterben
65638 Menschen würden monatlich beim fliegen sterben
</code></pre>
<p>Wie viele Flugzeuge müssten dafür abstürzen?</p>
<pre><code class="language-python">passagiere_pro_maschine = 101 # Quelle: https://de.statista.com/statistik/daten/studie/327019/umfrage/passagiere-je-flugzeug-deutsche-flughaefen/
fluege = fluggaeste_deutschland / passagiere_pro_maschine / 365
abstuerze = tote_fluggaeste_pro_tag / passagiere_pro_maschine
print(f&quot;{abstuerze:.0f} von {fluege:.0f} Flugzeugen würden täglich abstürzen&quot;)
</code></pre>
<pre><code>22 von 6158 Flugzeugen würden täglich abstürzen
</code></pre>
<h2>Wie sieht es mit dem Schienenverkehr aus?</h2>
<pre><code class="language-python">zuggaeste_deutschland = 2_600_000_000 # https://de.statista.com/statistik/daten/studie/13626/umfrage/reisende-im-schienenpersonenverkehr-der-db-ag/
tote_zuggaeste_pro_tag = prozent_tote_pro_tag * zuggaeste_deutschland
print(f&quot;{int(tote_zuggaeste_pro_tag)} Menschen würden täglich bei Zugfahrten sterben&quot;)
print(f&quot;{int(tote_zuggaeste_pro_tag * 7)} Menschen würden wöchentlich bei Zugfahrten sterben&quot;)
print(f&quot;{int(tote_zuggaeste_pro_tag * 30)} Menschen würden monatlich bei Zugfahrten sterben&quot;)
</code></pre>
<pre><code>25060 Menschen würden täglich bei Zugfahrten sterben
175421 Menschen würden wöchentlich bei Zugfahrten sterben
751807 Menschen würden monatlich bei Zugfahrten sterben
</code></pre>
<p>Wie viele Zugunglücke wären es pro Tag?</p>
<pre><code class="language-python">kapazitaet = 468 # Intercity 2 https://de.wikipedia.org/wiki/Intercity_2_(Deutsche_Bahn)
zuege = zuggaeste_deutschland / kapazitaet / 365
ungluecke = tote_zuggaeste_pro_tag / kapazitaet
print(f&quot;{ungluecke:.0f} von {zuege:.0f} Zügen würden täglich verunglücken&quot;)
</code></pre>
<pre><code>54 von 15221 Zügen würden täglich verunglücken
</code></pre>
<h2>Fazit</h2>
<p>Es ist schrecklich wie viele Menschen gerade an COVID-19 sterben.
Wenn man diese Werte in Relation setzt wird einem das Ausmaß erst richtig bewusst.
Niemand von uns würde in einen Zug oder Flugzeug steigen wenn es genau so gefährlich wäre wie es gerade in dieser Pandemie ist.</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/django-migrate-mysql-to-postgres/</guid>
      <title>Django Migrate MySQL to Postgres</title>
      <link>https://blog.libove.org/posts/django-migrate-mysql-to-postgres/</link>
      <pubDate>Thu, 17 Dec 2020 18:18:48 +0100</pubDate>
      <description><![CDATA[<p>I've recently migrated my <a href="https://tools.libove.org/">tools</a> project from an old the server to a new one.
In the process I've decided to change the used database from MySQL to PostgreSQL.
To do so I've used this <a href="https://www.calazan.com/migrating-django-app-from-mysql-to-postgresql/#disqus_thread">amazing guide from calazan.com</a>.</p>
<p>As the guide was written for django 1.6 some minor changes were necessary when working with newer versions (I've used django 3.1.4).</p>
<h2>Setup for migration</h2>
<p>Steps 1 + 2 work as described in the guide above. A new database is created and added to the <code>settings.py</code>.</p>
<pre><code>DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'dbname',
        'USER': 'dbuser',
        'PASSWORD': 'dbpass',
        'HOST': 'mysql.example.com',
        'PORT': '',
    },
    'postgresql': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'dbname',
        'USER': 'dbuser',
        'PASSWORD': 'dbpass',
        'HOST': 'postgresql.example.com',
        'PORT': '',
    }
}
</code></pre>
<h2>Apply migrations to new database</h2>
<pre><code>python manage.py migrate --database=postgresql
</code></pre>
<p><code>syncdb</code> is no longer available in newer django versions and was replaced by <code>migrate</code>.
The flag <code>--no-initial-data</code> is no longer available and I didn't find a substitution.</p>
<h2>Clean Up the new database</h2>
<p>Some migrations will create initial data in your database. This will probably create conflicts, as we are migrating existing data into this database.
I did this step by hand, but the command <code>python manage.py sqlflush --database=postgresql</code> will give you the SQL query to do it automatically.</p>
<h2>Copy data to new database</h2>
<p>In order to copy the data we use <code>dumpdata</code> and <code>loaddata</code>.</p>
<p>When dumping the data it is important to use the flag <code>--natural-foreign</code>, otherwise the import process cannot import data with foreign keys.</p>
<pre><code>python manage.py dumpdata --all --natural-foreign &gt; dump.json
</code></pre>
<p>The dumped data can be loaded into the new database. This process can take a long time. Ensure that your machine executing this command is as close as possible to the database, ideally running on the same machine. First, I started this command on my local machine. After 30 minutes I decided that it took too long and uploaded the dump to the server running the database, which finished the command in about a minute. My dump file was only 16 MB big.</p>
<pre><code>python manage.py loaddata dump.json --database=postgresql
</code></pre>
<h2>Adjust config</h2>
<p>Now all data is available in the new database. Just adjust the settings to use the <code>postgresql</code> as the <code>default</code> database.</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/fairy-ring/</guid>
      <title>Fairy Ring</title>
      <link>https://blog.libove.org/posts/fairy-ring/</link>
      <pubDate>Fri, 02 Oct 2020 21:40:33 +0200</pubDate>
      <description><![CDATA[<p>Mín tul- in sídh, mellon!</p>
<p><video width="100%" src="media/fairy_audio.webm" alt="Rendering of a portal with two light orbs orbiting above. Leafs fly away from the portal." controls></video></p>
<p><a href="media/fairy_audio.webm">Rendering of a portal with two light orbs orbiting above. Leafs fly away from the portal.</a></p>
<p>Music: Remix of <a href="https://www.scottbuckley.com.au/library/the-spaces-between/">Monomyth – The Spaces Between - Scott Buckly</a> CC-BY 4.0
</i></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/blood-ring/</guid>
      <title>C̸u̴r̶s̷e̷ ̶o̵f̵ ̴B̵l̷o̶o̸d̴</title>
      <link>https://blog.libove.org/posts/blood-ring/</link>
      <pubDate>Wed, 30 Sep 2020 19:05:33 +0200</pubDate>
      <description><![CDATA[<p>Nyarlathotep, ee ilyaa h'n'ghft llll shugg lloig throd</p>
<p>nafluh'e Chaugnar Faugn ch'.</p>
<p>H̸a̶s̶t̷u̷r̶o̸r̷ ̸i̸l̵y̴a̴a̴ ̸g̵o̷f̵'̴n̸n̶o̷g̸ ̵s̸h̵u̸g̷g̷ ̶p̴h̴'̴u̴h̸'̸e̵ ̵t̵h̴r̶o̶d̸ ̷a̶t̷h̶g̸</p>
<p>v̷͈̓u̸͍̓l̴̝͌g̶̮͠t̷̜͆m̴̗̿ ̷̪̈k̵̮͑ň̷̤'̶͍̊a̵̝̽ ̸̫̐ṇ̵̊a̸͈̅f̸̘̊l̶̺͠ó̴̢r̷̂ͅȓ̴̨'̸̡̓è̷̹,̸̘̈́</p>
<p>c̷̹͝ȋ̶̟l̴̨̈́y̸̨̽ạ̴̊â̴͇ ̴̙̂c̸̭͋h̵̲̓'̸͎͗ ̷͕́s̸̲̾ḧ̴̢́ů̶͈g̸̈́͜g̴̢̅o̴̧̔ṱ̴͆h̸̪̃ ̶̹̆h̴̲̊ủ̷̙p̸͇̒a̵̪͆d̵̲͗g̷̖̊h̸̳̋ ̷̖̊ń̸̥a̴̖͌f̷̦͝l̶̯̊'̵̞̃f̷̳̒h̸̢͋á̴͜l̵͕̐m̴̳͑a̶̢̾ ̸̘̈́e̵̯̎b̶͍͝u̵̮͘n̸̻̎m̵̺̃ả̴̝o̷̩͊r̸̨͝</p>
<p>b̷̼̏ṵ̷͋g̶̥̈ ̶̧͂'̸̭̉b̴̮͛ṱ̶͘h̵̬́n̵̝̊ḵ̸̄ǫ̶̛t̷̛̳h̶̹̿ ̸́͜H̴̜̿a̴̛̲s̴̡̓t̶̢̚u̶̢͆r̶̦̔ ̴̰̃f̸͍͘h̸̖̄t̵̛̹a̶̯̽g̸̮͠n̷͓͒ ̶͇̾s̴̖̄ḩ̴͆a̵͔̔g̷͗ͅg̵͕͠.̷̰̆</p>
<p><video width="100%" src="media/blood_audio.webm" alt="Rendering of a ring with floating sphere oozing blood." controls></video></p>
<p><a href="media/blood_audio.webm">Blood Ring</a></p>
<p>Music: Remix of <a href="https://www.scottbuckley.com.au/library/the-old-ones/">THE OLD ONES - Scott Buckly</a> CC-BY 4.0
</i></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/kommunalwahlen-aachen-2020/</guid>
      <title>Kommunalwahlen Aachen 2020</title>
      <link>https://blog.libove.org/posts/kommunalwahlen-aachen-2020/</link>
      <pubDate>Sun, 20 Sep 2020 21:58:59 +0200</pubDate>
      <description><![CDATA[<p>Um die Ergebnisse der Kommunalwahlen in Aachen besser zu verstehen habe ich die Ergebnisse der einzelnen Stimmbezirke in einer Kartenansicht visualisiert. Hierbei habe ich für jede Partei eine Grafik erstellt, die zeigt wo die Partei besonders stark ist.</p>
<p>Quelle: <a href="https://wahlen.regioit.de/1/km2020/05334002/html5/Kreistagswahl_NRW_78_Uebersicht_stbz.html">https://wahlen.regioit.de/1/km2020/05334002/html5/Kreistagswahl_NRW_78_Uebersicht_stbz.html</a></p>
<p>![Karte mit Wahlergebnissen für Städteregion Aachen: CDU](media/Städteregion: CDU.png)
![Karte mit Wahlergebnissen für Städteregion Aachen: GRÜNE](media/Städteregion: GRÜNE.png)
![Karte mit Wahlergebnissen für Städteregion Aachen: SPD](media/Städteregion: SPD.png)
![Karte mit Wahlergebnissen für Städteregion Aachen: LINKE](media/Städteregion: LINKE.png)
![Karte mit Wahlergebnissen für Städteregion Aachen: FDP](media/Städteregion: FDP.png)
![Karte mit Wahlergebnissen für Städteregion Aachen: PIRATEN](media/Städteregion: PIRATEN.png)
![Karte mit Wahlergebnissen für Städteregion Aachen: UWG](media/Städteregion: UWG.png)
![Karte mit Wahlergebnissen für Städteregion Aachen: AfD](media/Städteregion: AfD.png)
![Karte mit Wahlergebnissen für Städteregion Aachen: PARTEI](media/Städteregion: PARTEI.png)</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/home-office-pizza/</guid>
      <title>Home Office Pizza</title>
      <link>https://blog.libove.org/posts/home-office-pizza/</link>
      <pubDate>Tue, 25 Aug 2020 18:04:58 +0200</pubDate>
      <description><![CDATA[<p><a href="/media/vegan_pizza.jpg"><img src="/thumbnail/vegan_pizza.jpg" alt="Vegan Pizza with bell peppers, onions and basil"></a></p>
<p>This is my pizza recipe for an easy to prepare lunch. It takes 15 minutes to prepare the dough. I typically prepare enough dough for 4 servings. The dough can be stored for several days and improves each day ;).</p>
<p>Preparing the pizza takes around 10 minutes.</p>
<h2>Ingredients</h2>
<h3>Dough (per serving):</h3>
<ul>
<li>175g wheat flour <a href="https://en.wikipedia.org/wiki/Flour#Type_numbers">(all purpose / 550)</a></li>
<li>100g water</li>
<li>5g salt</li>
<li>5g olive oil</li>
<li>~5g yeast</li>
</ul>
<h3>Toppings</h3>
<ul>
<li>Passata</li>
<li>Olive Oil</li>
<li>Salt and pepper</li>
<li>(Chili, optional)</li>
<li>Oregano</li>
<li>vegan cheese (I use <a href="https://www.simply-v.de/en/simply-v-vegan-grated.html">Simply V</a>)</li>
<li>whatever you like on a pizza!</li>
</ul>
<h2>Preparation</h2>
<ul>
<li>Mix your flour and salt</li>
<li>Dissolve your yeast in the water</li>
<li>Add water and oil to the dry ingredients and mix everything</li>
<li>Once all liquid is absorbed start kneading the dough until you reach an even consistency</li>
<li>Put your dough in the fridge overnight</li>
</ul>
<h2>Cooking</h2>
<p>In the morning take 275g of your dough out of the fridge. Fold or knead it shortly to create a round ball. Put the ball into a bowl and cover it.</p>
<p>After 1-3 hours fold the dough once. Be careful to keep the air building up!</p>
<p>Preheat your oven to 250°C (20-30 minutes before your lunch).</p>
<p>Put the dough ball on a floured surface and carefully push and pull it into shape. Be gentle and keep as much air as possible inside the dough! Put the pizza dough on your baking sheet.</p>
<p>Coat the pizza lightly with olive oil. Add 3-5 tablespoon passata and spread it evenly. You don't need a lot of sauce, only cover the dough lightly.</p>
<p>Season the sauce with salt, pepper and oregano (I also add chili). Add your ingredients and cheese.</p>
<p>Bake the pizza for 10 minutes.</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/wealth-tax/</guid>
      <title>Wealth Tax</title>
      <link>https://blog.libove.org/posts/wealth-tax/</link>
      <pubDate>Tue, 18 Aug 2020 20:49:26 +0200</pubDate>
      <description><![CDATA[<p><a href="http://paulgraham.com/wtax.html">Paul Graham wrote an article about wealth tax</a>. In the article he 'models' the effect of a wealth tax on the fortune of a person. His assumption for the modeling is a person who earned a lot of money with a startup in her 20s. Paul Graham shows how much of the earned stocks/money would be taken by the government by a wealth tax.</p>
<p>Paul Graham comes to the conclusion that wealth taxes have &quot;dramatic effects&quot; and that &quot;even a .5% wealth tax would start to keep founders away from a state or country that imposed it.&quot;</p>
<p>I think this conclusion is wrong. His model assumes that the earned money just lies on a big pile and is not used for 60 years. A more reasonable model would include interest being earn with the money. For this I will use an extended model:</p>
<p><code>(growth * (1-wealth_tax)^years</code></p>
<p>If we assume a reasonable return of investment of 2% per year the number shifts dramatically. Instead of &quot;losing&quot; 25% at 0.5% wealth tax you end up with 242% of your original value after 60 years. Even a wealth tax of 3% would only half your wealth.</p>
<table>
<thead>
<tr>
<th>Wealth Tax</th>
<th>Graham Model</th>
<th>With 2% Interest</th>
</tr>
</thead>
<tbody>
<tr>
<td>0.10%</td>
<td>94.17%</td>
<td>308.99%</td>
</tr>
<tr>
<td>0.50%</td>
<td>74.03%</td>
<td>242.88%</td>
</tr>
<tr>
<td>1.00%</td>
<td>54.72%</td>
<td>179.52%</td>
</tr>
<tr>
<td>2.00%</td>
<td>29.76%</td>
<td>97.63%</td>
</tr>
<tr>
<td>3.00%</td>
<td>16.08%</td>
<td>52.76%</td>
</tr>
<tr>
<td>4.00%</td>
<td>8.64%</td>
<td>28.33%</td>
</tr>
<tr>
<td>5.00%</td>
<td>4.61%</td>
<td>15.12%</td>
</tr>
</tbody>
</table>
<p>Paul Graham's article also misses to mention that a wealth tax only affect people with ALOT of money. Elizabeth Warren's <a href="https://elizabethwarren.com/plans/ultra-millionaire-tax">&quot;Ultra-Millionaire Tax&quot;</a> would only tax assets over $50 million. Once you are affected by a wealth tax you have enough money to live a luxurious life (and your children will probably never have to work to live). Most startup founders never reach this amount of wealth.</p>
<p>As this tax only targets the super rich let's look at the effect this tax would have on Jeff Bezos (data source: <a href="https://en.wikipedia.org/wiki/Jeff_Bezos">Wikipedia</a>):</p>
<p><a href="/media/bezos_wealth_tax.png"><img src="/thumbnail/bezos_wealth_tax.png" alt="Effect of Wealth Tax on Jeff Bezos"></a></p>
<p>Even a high wealth tax of 5% leaves Bezos the majority of his wealth. If Bezos stopped earning new wealth in 2018 he could bequeath $30 billion to his children in 2080 with a 2% wealth tax. With a 5% wealth tax his children would still inherit over $4 billion!</p>
<p>The questions I'm asking myself when thinking about a wealth tax are the following:</p>
<ul>
<li>Should a person have virtually unlimited resources for the rest of her life, just because she had a clever idea in her 20s?</li>
<li>Should children have virtually unlimited resources for the rest of their lives, just because one of their parents had a clever idea in her 20s?</li>
<li>Should grandchildren have virtually unlimited resources for the rest of their lives, just because one of their grandparents had a clever idea in her 20s?</li>
</ul>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/den-haag-office/</guid>
      <title>Contrast between old and new building</title>
      <link>https://blog.libove.org/posts/den-haag-office/</link>
      <pubDate>Mon, 06 Jul 2020 21:45:13 +0200</pubDate>
      <description><![CDATA[<a href="/media/den_haag_4.jpeg" class="u-photo">
    <img src="/thumbnail/den_haag_4.jpeg" alt="Contrast between old and new building" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/den-haag-facade/</guid>
      <title>Building Facade with flowers</title>
      <link>https://blog.libove.org/posts/den-haag-facade/</link>
      <pubDate>Mon, 06 Jul 2020 21:45:13 +0200</pubDate>
      <description><![CDATA[<a href="/media/den_haag_1.jpeg" class="u-photo">
    <img src="/thumbnail/den_haag_1.jpeg" alt="Building Facade with flowers" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/den-haag/</guid>
      <title>Den Haag</title>
      <link>https://blog.libove.org/posts/den-haag/</link>
      <pubDate>Mon, 06 Jul 2020 21:45:13 +0200</pubDate>
      <description><![CDATA[<p><a href="/media/den_haag_1.jpeg"><img src="/thumbnail/den_haag_1.jpeg" alt="Building Facade with flowers"></a>
<a href="/media/den_haag_2.jpeg"><img src="/thumbnail/den_haag_2.jpeg" alt="Binnenhof"></a>
<a href="/media/den_haag_3.jpeg"><img src="/thumbnail/den_haag_3.jpeg" alt="Dunes"></a>
<a href="/media/den_haag_4.jpeg"><img src="/thumbnail/den_haag_4.jpeg" alt="Contrast between old and new building"></a>
<a href="/media/den_haag_5.jpeg"><img src="/thumbnail/den_haag_5.jpeg" alt="World Peace Flame"></a></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/den-haag-memorial/</guid>
      <title>World Peace Flame</title>
      <link>https://blog.libove.org/posts/den-haag-memorial/</link>
      <pubDate>Mon, 06 Jul 2020 21:45:13 +0200</pubDate>
      <description><![CDATA[<a href="/media/den_haag_5.jpeg" class="u-photo">
    <img src="/thumbnail/den_haag_5.jpeg" alt="World Peace Flame" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/den-haag-beach/</guid>
      <title>Dunes</title>
      <link>https://blog.libove.org/posts/den-haag-beach/</link>
      <pubDate>Mon, 06 Jul 2020 21:45:13 +0200</pubDate>
      <description><![CDATA[<a href="/media/den_haag_3.jpeg" class="u-photo">
    <img src="/thumbnail/den_haag_3.jpeg" alt="Dunes" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/den-haag-castle/</guid>
      <title>Binnenhof</title>
      <link>https://blog.libove.org/posts/den-haag-castle/</link>
      <pubDate>Mon, 06 Jul 2020 21:45:13 +0200</pubDate>
      <description><![CDATA[<a href="/media/den_haag_2.jpeg" class="u-photo">
    <img src="/thumbnail/den_haag_2.jpeg" alt="Binnenhof" />
</a>



]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/mehrwertsteuer-senkung/</guid>
      <title>Mehrwertsteuersenkung</title>
      <link>https://blog.libove.org/posts/mehrwertsteuer-senkung/</link>
      <pubDate>Fri, 03 Jul 2020 21:49:02 +0200</pubDate>
      <description><![CDATA[<p>Zum 1. Juli 2020 wurde in Deutschland, als Teil des Corona-Konjunkturpakets, <a href="https://www.tagesschau.de/inland/konjunkturpaket-113.html">die Mehrwertsteuer von 19% auf 16%, bzw von 7% auf 5%, gesenkt</a>. Schon bei der Ankündigung der Senkung wurde spekuliert ob diese tatsächlich an den Verbraucher weitergegeben wird, oder ob das Geld in den Konzernen stecken bleibt.</p>
<p>Um zu überprüfen wie mit der Senkung umgegangen wird habe ich kurz nach der Ankündigung angefangen Preisdaten zu sammeln. Hierfür habe ich die Webseiten von 10 deutschen Unternehmen stichprobenartig beobachtet. Dabei wurde täglich der Preis aller gelisteten Artikel gespeichert (max 25000 Artikel pro Unternehmen). Beobachtet habe ich die Unternehmen:</p>
<ul>
<li>Aldi Nord</li>
<li>Aldi Süd</li>
<li>BMW</li>
<li>Kaufland</li>
<li>Hornbach</li>
<li>Media Markt</li>
<li>Obi</li>
<li>Netto</li>
<li>Avocado Store</li>
<li>Whisky.de</li>
</ul>
<p>Ich hätte gerne noch mehr Unternehmen beobachtet, jedoch ließen sich viele Webseiten nicht reibungslos auswerten. Insgesamt habe ich 3.228.617 Preise von 368.452 Produkten erfasst (Stand: 03.07.20).</p>
<h2>Auswertung</h2>
<p>Als leichter Einstieg in die Auswertung dient die Preisentwicklung bei BMW. Die Preise bleiben konstant bis zum 2. Juli (da ich die Daten nachts erhoben habe kann es durchaus sein das die Preise schon am 1. Juli gesenkt wurden) und sinken dann um etwa 3% ab. Hier wurde die Mehrwertsteuersenkung direkt an den Kunden weitergegeben.</p>
<p><a href="/media/bmw.png"><img src="/thumbnail/bmw.png" alt="Preisentwicklung bei BMW"></a></p>
<p>An diesem Graphen lässt sich die Visualisierung leicht erklären. Für jeden Artikel berechene ich täglich den &quot;relativen Preis&quot;. Dafür gucke ich mir die Preise eines Artikels über den gesamten Beobachtungszeitraum an und nehme an das der &quot;normale&quot; Preis der am häufigsten vorkommende Preis ist. Dann werden alle Preise durch diesen normalen Preis geteilt um den relativen Preis zu bekommen. Auf diese weise lassen sich die Preise normieren, was Auswertung und Visualisierung deutlich vereinfachen.</p>
<p>Etwas spannender wird das ganze schon bei Aldi Nord.</p>
<p><a href="/media/aldi-nord.png"><img src="/thumbnail/aldi-nord.png" alt="Preisentwicklung bei Aldi Nord"></a></p>
<p>Hier kann man sehen das die Preise immer wieder abweichen. Die schwachen Linien ober und unterhalb der Hauptlinie zeigen Preisänderungen. Zu Beginn der Beobachtung sieht man Rabattaktionen in denen wenige Artikel günstiger sind als normalerweise. Das erste interessante Ereignis gibt es am 21.6. Hier werden viele Artikel ca. 3% teurer. Am 27.6 fallen all diese Artikel wieder auf ihr normales Niveau zurück und gleichzeitig werden andere Artikel ca. 3% billiger. Mit Beginn der der Mehrwertsteuersenkung werden die meisten Artikel wieder teurer und nur wenige bleiben um 3% reduziert.</p>
<p>Auf den ersten Blick sieht es so aus als würde Aldi Nord die Ersparnisse durch das Konjunkturpaket nicht an ihre Kunden weitergeben. Jedoch scheint sich Aldi Nord dafür entschieden zu haben nicht die Preise anzupassen, sonder einen <a href="https://www.aldi-nord.de/themenwelten/der-Original-ALDI-Preis/hinweis-zur-mehrwertsteuersenkung.html">Rabatt auf den Einkauf zu geben</a>.</p>
<p>Ähnlich sieht es bei Aldi Süd aus. Hier gab es jedoch keine Preiserhöhung oder -senkung vor der Mehrwertsteuersenkung</p>
<p><a href="/media/aldi-sued.png"><img src="/thumbnail/aldi-sued.png" alt="Preisentwicklung bei Aldi Süd"></a></p>
<p>Der Konkurrent Kaufland hat sich scheinbar entschieden die Preise selbst zu senken.</p>
<p><a href="/media/kaufland.png"><img src="/thumbnail/kaufland.png" alt="Preisentwicklung bei Kaufland"></a></p>
<p>Wenige Artikel wurden zwei Wochen vor der Senkung teurer, aber ein ähnlicher (schwächerer) Effekt lässt sich am Anfang der Beobachtung erkennen. Eine Woche vor der Senkung hat Kaufland schrittweise angefangen die Preise anzupassen, jedoch sind nicht alle Preise gesenkt worden. Hier wäre eine detailliertere Analyse notwendig um zu sehen um welche und wieviel Artikel es sich handelt.</p>
<p>Deutlich chaotischer scheint es bei Netto zuzugehen.</p>
<p><a href="/media/netto.png"><img src="/thumbnail/netto.png" alt="Preisentwicklung bei Netto"></a></p>
<p>Hier gibt es ständige Preisänderungen und Rabattaktionen die viele Artikel betreffen. Auch hier sieht man, dass ab dem 27.6 die Preise an die neue Mehrwertsteuer angepasst wurden. Auffällig hier sind die angehobenen Preise am 18.6 und die zum 27.6 immer dicker werdende Linie bei etwa 3%. Hier sollte noch etwas genauer nachgeguckt werden. Es könnte sein, dass hier Preise vor der Senkungen angehoben wurden.</p>
<p>Der Preisgraph für MediaMarkt sieht ähnlich chaotisch aus.</p>
<p><a href="/media/mediamarkt.png"><img src="/thumbnail/mediamarkt.png" alt="Preisentwicklung bei MediaMarkt"></a></p>
<p>Hier lässt sich direkt die Preissenkung am 1.7 erkennen. Scheinbar wurden hier pauschal alle Preise gesenkt. Auf den ersten Blick lässt sich auch nicht erkennen, dass Preise vorher erhöht wurden.</p>
<p>Bei OBI wurden die Preise schon am 25.6 gesenkt. Was es mit dem &quot;Zick-Zack&quot; Muster auf sich hat weiß ich noch nicht, es könnte aber ein Artefakt meiner Erhebung oder Visualisierung sein.</p>
<p><a href="/media/obi.png"><img src="/thumbnail/obi.png" alt="Preisentwicklung bei OBI"></a></p>
<p>Bei Hornbach wurden viele Preise gesenkt aber bei weitem nicht alle.</p>
<p><a href="/media/hornbach.png"><img src="/thumbnail/hornbach.png" alt="Preisentwicklung bei Hornbach"></a></p>
<p>Bei Avocado Store handelt es sich um einen Marktplatz (ähnlich zu Amazon). Viele Anbieter können hier ihre Waren anbieten.</p>
<p><a href="/media/avocadostore.png"><img src="/thumbnail/avocadostore.png" alt="Preisentwicklung bei Avocado Store"></a></p>
<p>Einige Anbieter scheinen sich entschlossen zu haben die Preise zu senken. Auch hier zeigt sich ein leichtes &quot;Zick-Zack&quot; Muster (auch hier könnte es ein Artefakt meiner Erhebung oder Visualisierung sein). Hier wird interessant ob sich mit der Zeit mehr und mehr Anbieter entschließen die Senkung an den Kunden weiterzugeben. Ich weiß nicht wie viel Einfluss die Plattform selbst darauf hat.</p>
<p>Whisky.de habe ich als Beispiel für ein spezialisiertes, kleines Unternehmen mit in die Analyse aufgenommen. Hier zeigt sich bis jetzt keine Senkung der Preise.</p>
<p><a href="/media/whisky.png"><img src="/thumbnail/whisky.png" alt="Preisentwicklung bei Whisky.de"></a></p>
<p>Insgesamt lässt sich sehen das die Mehrwertsteuersenkung an den Verbraucher weitergeben wird. An einigen Stellen sieht es noch so aus als wäre es nicht überall der Fall, aber dies sind bis jetzt nur Hypothesen die weiter untersucht werden sollten! Ich werde meine Scraper vorerst weiter laufen lassen und tiefer in die Daten eintauchen.</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/rainbow-log/</guid>
      <title>Rainbow Log</title>
      <link>https://blog.libove.org/posts/rainbow-log/</link>
      <pubDate>Sat, 29 Feb 2020 16:47:55 +0100</pubDate>
      <description><![CDATA[<p>Inspired by: <a href="https://www.reddit.com/r/ProgrammerHumor/comments/fb9cd3/i_thought_consolelog_needed_some_more_flair/">I thought console.log() needed some more flair</a>
and <a href="https://gist.github.com/Benargee/abf0ceba9baa92a9a2a2d0f2952b6928">Benargee/rainbowLog.js</a></p>
<pre><code class="language-js">console.rainbow = (s) =&gt; {
    const [cs, c] = s
        .split(&quot;&quot;)
        .map((c,i) =&gt; ['%c'+c, `color:hsl(${20*i%360}, 100%, 50%)`])
        .reduce((p, c) =&gt; [p[0] + c[0], p[1].concat(c[1])], [&quot;&quot;, []])
  console.log(cs, ...c)
}
console.rainbow(&quot;Hello World!&quot;)
</code></pre>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/kessler-bomb/</guid>
      <title>Kessler Bomb</title>
      <link>https://blog.libove.org/posts/kessler-bomb/</link>
      <pubDate>Thu, 13 Feb 2020 22:26:58 +0100</pubDate>
      <description><![CDATA[<blockquote>
<h2>Kessler Bomb</h2>
<h6>Siege Weapon</h6>
<p>Deployed in low orbits around hostile planets.</p>
<p>On deployment millions of projectiles are scatters in random orbits around the planet, taking out all satellites after a few hours.
The shrapnels will make launches from the attacked planet impossible for several decades.</p>
<p>Primarily used to lock down planets with offensive capabilities.</p>
<p><em>Prohibited by the New Geneva Convention, due to the high collateral damage</em></p>
</blockquote>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2020-01-16-thoughts-on-development-time-estimation/</guid>
      <title>Thoughts on Development Time Estimation</title>
      <link>https://blog.libove.org/posts/2020-01-16-thoughts-on-development-time-estimation/</link>
      <pubDate>Thu, 16 Jan 2020 19:18:49 +0000</pubDate>
      <description><![CDATA[<p>Estimating development time is similar to the <a href="https://en.wikipedia.org/wiki/Coastline_paradox">coastline paradox</a>. The more detailed your project description gets, the longer you estimation will be.</p>
<p><img src="https://upload.wikimedia.org/wikipedia/commons/2/20/Britain-fractal-coastline-combined.jpg" alt="CC BY-SA 3.0 made by Avsa mixed by Acadac">
<a href="https://en.wikipedia.org/wiki/User:Acadac">Graphic by: Acadac</a></p>
<p>You start with &quot;building a blog will take me 20 hours&quot;, then go to &quot;OK actually I will need 10 hours for the data structure, 10 hours for a way to create posts and 10 hours for the actual website&quot;. For each of this estimations you will end up with more hours needed the further you break them down.</p>
<p>Breaking tasks down reveals more of the complexity of a task, leading to higher (hopefully more accurate) estimations.</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2019-09-18-global-climate-strike/</guid>
      <title>Global Climate Strike</title>
      <link>https://blog.libove.org/posts/2019-09-18-global-climate-strike/</link>
      <pubDate>Wed, 18 Sep 2019 18:53:51 +0000</pubDate>
      <description><![CDATA[<p><a href="/media/strike-logo-EN-color.png"><img src="/thumbnail/strike-logo-EN-color.png" alt=""></a></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2019-08-17-debugging-web-apis-with-session-based-authentication-in-postman/</guid>
      <title>Debugging Web APIs with session based authentication in Postman</title>
      <link>https://blog.libove.org/posts/2019-08-17-debugging-web-apis-with-session-based-authentication-in-postman/</link>
      <pubDate>Sat, 17 Aug 2019 10:25:43 +0000</pubDate>
      <description><![CDATA[<ul>
<li>Open the web app in Chrome or Firefox and open the Developer Tools (<strong>F12</strong> or <strong>Ctrl+Shift+I</strong>).</li>
<li>Login and look for an API call in the <strong>Network</strong> Tab of the Developer Tools.</li>
<li>Right click the request and select <em><strong>Copy -&gt; Copy as cURL</strong></em></li>
</ul>
<p><a href="/media/image-3.png"><img src="/thumbnail/image-3.png" alt=""></a></p>
<ul>
<li>Open Postman and use the <strong>Import</strong> Button -&gt; <strong>Paste Raw Text</strong></li>
</ul>
<p><a href="/media/image-2.png"><img src="/thumbnail/image-2.png" alt=""></a></p>
<ul>
<li>You can now execute the same request as done in the browser.</li>
<li>Start modifying it and happy hacking.</li>
</ul>
<p><span class="hashtag"><a class="p-category" href="/tags/debug/">#debug</a></span> <span class="hashtag"><a class="p-category" href="/tags/web/">#web</a></span> <span class="hashtag"><a class="p-category" href="/tags/postman/">#postman</a></span></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2019-07-28-lessons-learnt-first-hackathon/</guid>
      <title>Lessons learnt: First Hackathon</title>
      <link>https://blog.libove.org/posts/2019-07-28-lessons-learnt-first-hackathon/</link>
      <pubDate>Sun, 28 Jul 2019 20:15:10 +0000</pubDate>
      <description><![CDATA[<ul>
<li>Be prepared. In my case the material was sent out a week beforehand. Use the time to setup your machine(s) and code base so you can start directly.</li>
<li>Do research on the topic. I've not looked into state of the art algorithms for the problem beforehand. This cost me a lot of time in the Hackathon, to understand the algorithm we used.</li>
<li>Have compute resources ready. I could have used 2 further machines, but forgot to setup ssh, Teamviewer or any other remote control on them.</li>
<li>Do whatever you know best. Some of my team members had far more experience on the topic than me. I've tried to follow there example, which wasn't successful. I think I would have been more successful using more basic approaches (which I have enough knowledge about) than trying to follow the &quot;state of the art&quot; approach. At the end I've tried to support them as best as I could (mostly doing evaluations).</li>
<li>Push for more organization. We quickly came to the conclusion that only 1 or 2 approaches (transfer learning on common models) would be feasible in the short time of a hackathon. This led to us being rather unorganized. Everybody tried to get the models running as fast as possible and tweaking them the rest of the time. I think we could have produced more insight on the topic with regular &quot;stand ups&quot;.</li>
</ul>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2019-01-19-autotexture/</guid>
      <title>AutoTexture</title>
      <link>https://blog.libove.org/posts/2019-01-19-autotexture/</link>
      <pubDate>Sat, 19 Jan 2019 18:10:15 +0000</pubDate>
      <description><![CDATA[<p>I've built a small tool to create tileable, seamless textures.</p>
<p><a href="https://autotexture.libove.org/">https://autotexture.libove.org/</a></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2018-10-15-commute-to-work/</guid>
      <title>Commute to Work</title>
      <link>https://blog.libove.org/posts/2018-10-15-commute-to-work/</link>
      <pubDate>Mon, 15 Oct 2018 18:40:25 +0000</pubDate>
      <description><![CDATA[<p><img src="media/IMG_0624.jpg" alt=""></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2018-08-22-helsinki-view-on-city/</guid>
      <title>[Helsinki] View on City</title>
      <link>https://blog.libove.org/posts/2018-08-22-helsinki-view-on-city/</link>
      <pubDate>Wed, 22 Aug 2018 16:07:17 +0000</pubDate>
      <description><![CDATA[<p><img src="media/DSC03702.jpg" alt="View on City"></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2018-08-21-helsinki-serenity/</guid>
      <title>[Helsinki] Serenity</title>
      <link>https://blog.libove.org/posts/2018-08-21-helsinki-serenity/</link>
      <pubDate>Tue, 21 Aug 2018 16:05:36 +0000</pubDate>
      <description><![CDATA[<p><img src="media/DSC03698.jpg" alt="Serenity&quot; titl=&quot;Serenity"></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2018-08-20-helsinki-pond-on-island/</guid>
      <title>[Helsinki] Pond on Island</title>
      <link>https://blog.libove.org/posts/2018-08-20-helsinki-pond-on-island/</link>
      <pubDate>Mon, 20 Aug 2018 16:04:21 +0000</pubDate>
      <description><![CDATA[<p><img src="media/DSC03664.jpg" alt="Pond on Island"></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2018-08-19-squirrel-in-helsinki/</guid>
      <title>[Helsinki] Squirrel</title>
      <link>https://blog.libove.org/posts/2018-08-19-squirrel-in-helsinki/</link>
      <pubDate>Sun, 19 Aug 2018 16:04:16 +0000</pubDate>
      <description><![CDATA[<p><a href="/media/DSC03761.jpg"><img src="/thumbnail/DSC03761.jpg" alt="Squirrel in Helsinki"></a></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2018-06-19-visualizing-likert-scale-survey-data/</guid>
      <title>Visualizing Likert Scale Survey Data</title>
      <link>https://blog.libove.org/posts/2018-06-19-visualizing-likert-scale-survey-data/</link>
      <pubDate>Tue, 19 Jun 2018 19:21:42 +0000</pubDate>
      <description><![CDATA[<p>I'm currently helping to evaluating a large market research survey, which ueses <a href="https://en.wikipedia.org/wiki/Likert_scale">Likert Scales</a>. To visualize the data I've tried several plots. The plots below where created with artificially created data to expose the strengths and weaknesses of different plot types.</p>
<p><strong>Data distribution:</strong></p>
<p> </p>
<p><a href="https://gist.github.com/h4kor/d3f619ab974d74dfb85fcdd816877a13">You can find the code for all plots here!</a></p>
<p> </p>
<h2>Bar Plot: Mean Values</h2>
<h4>Pros:</h4>
<ul>
<li>Easy to create</li>
<li>Simple to read</li>
<li>Q3 – Q5 distinguishable</li>
</ul>
<h4>Cons:</h4>
<ul>
<li>Hides a lot complexity</li>
<li>Doesn't show spread</li>
<li>Creates high confidence in shown values</li>
</ul>
<p><a href="/media/mean.png"><img src="/thumbnail/mean.png" alt="Likert Scale Mean"></a></p>
<h2>Bar Plot: Mean values and Standard Deviation</h2>
<p>Extension of first plot.</p>
<h4>Pros:</h4>
<ul>
<li>Still simple</li>
<li>Introduces skepticism into shown data</li>
<li>Hints at spread in data</li>
</ul>
<h4>Cons:</h4>
<ul>
<li>Hides a lot complexity</li>
<li>Still doesn't show distribution</li>
</ul>
<p><a href="/media/mean_std.png"><img src="/thumbnail/mean_std.png" alt="Likert Scale Mean + Std"></a></p>
<h2>Violin Plot</h2>
<h4>Pros:</h4>
<ul>
<li>High Resolution</li>
<li>Shows distribution of data</li>
</ul>
<h4>Cons:</h4>
<ul>
<li>Harder to read</li>
<li>Noisy on small sample sizes</li>
<li>Shows data as being continuous</li>
</ul>
<p><a href="/media/violin.png"><img src="/thumbnail/violin.png" alt="Likert Scale Violin"></a></p>
<h2>Vertical Histograms</h2>
<h4>Pros:</h4>
<ul>
<li>High Resolution</li>
<li>Shows distribution of data</li>
</ul>
<h4>Cons:</h4>
<ul>
<li>Harder to read</li>
<li>Shows data as being continuous</li>
</ul>
<p><a href="/media/hists.png"><img src="/thumbnail/hists.png" alt="Likert Scale Histogram"></a></p>
<h2>Scatter Plot</h2>
<h4>Pros:</h4>
<ul>
<li>High Resolution</li>
<li>Actually shows complete data</li>
<li>Shows distribution of data</li>
</ul>
<h4>Cons:</h4>
<ul>
<li>Hard to read</li>
<li>Introduced noise and overdraw can distort data</li>
</ul>
<p><a href="/media/scatter-1.png"><img src="/thumbnail/scatter-1.png" alt=""></a></p>
<h2>Scaled Dots</h2>
<h4>Pros:</h4>
<ul>
<li>High Resolution</li>
<li>Shows distribution of data</li>
<li>Shows data as discrete values</li>
</ul>
<h4>Cons:</h4>
<ul>
<li>Visual distortion of proportions</li>
<li>Humans can't easily compare circle sizes</li>
</ul>
<p><a href="/media/scaled_dots.png"><img src="/thumbnail/scaled_dots.png" alt="Likert Scale Scaled Dots"></a></p>
<h2>Scaled Dots</h2>
<h4>Pros:</h4>
<ul>
<li>Looks scientific</li>
<li>Contains Median, Quantiles</li>
<li>Shows outliers</li>
</ul>
<h4>Cons:</h4>
<ul>
<li>Not designed for discrete data</li>
<li>Doesn't show distribution correctly (e.g. Q4 + Q5)</li>
</ul>
<p><a href="/media/boxplot.png"><img src="/thumbnail/boxplot.png" alt="Likert Scale Boxplot"></a></p>
<p> </p>
<p><span class="hashtag"><a class="p-category" href="/tags/statistics/">#statistics</a></span> <span class="hashtag"><a class="p-category" href="/tags/data/">#data</a></span> <span class="hashtag"><a class="p-category" href="/tags/datascience/">#datascience</a></span> <span class="hashtag"><a class="p-category" href="/tags/likert/">#likert</a></span></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2018-06-13-morphing-pizza/</guid>
      <title>Morphing Pizza</title>
      <link>https://blog.libove.org/posts/2018-06-13-morphing-pizza/</link>
      <pubDate>Wed, 13 Jun 2018 20:25:40 +0000</pubDate>
      <description><![CDATA[<p><a href="https://www.youtube.com/embed/4fFMpa_JsyI">Morphing Pizza</a></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2018-06-13-f1-score-rises-while-loss-keeps-increasing/</guid>
      <title>F1-Score rises while Loss keeps increasing</title>
      <link>https://blog.libove.org/posts/2018-06-13-f1-score-rises-while-loss-keeps-increasing/</link>
      <pubDate>Wed, 13 Jun 2018 20:14:53 +0000</pubDate>
      <description><![CDATA[<p>I've recently run into a paradoxical situation while training a network to distinguish between to classes.</p>
<p>I've used cross entropy as my loss of choice. On my training set the loss steadily decreased while the <em>F1-Score</em> improved. On the validation set the loss decreased shortly before increasing and leveling off around <strong>~2</strong>, normally a clear sign for overfitting. However the <em>F1-Score</em> on the validation set kept rising and reached <strong>~0.92</strong>, with similarly high <em>presicion</em> and <em>recall</em>.</p>
<p>As I never took a closer look at the relation between F1-Score and the cross-entropy loss, I've decided to do a quick simulation and plotted the results. The plots show the cross-entropy in relation to the F1-Score. On the first graph I varied the range in which the misses landed, while hits where always perfect. The graph shows that misses with a _confidence _create higher losses, even at high F1-Scores. In contrast the <em>confidence</em> of hits has a negligible influence on the loss as depicted in the second graph.</p>
<p>It seems likely that my network developed a high <em>confidence</em> in its prediction, only answering **** or <strong>1</strong>, which provoked a high loss while still achieving reasonable high accuracy.</p>
<p> </p>
<p><img src="media/vary_miss.png" alt="F1-Score and Cross Entropy with varying Miss Ranges"></p>
<p><img src="media/vary_hit.png" alt="F1-Score and Cross Entropy with varying Confidence"></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2018-01-30-human-machine-co-creation/</guid>
      <title>Human Machine Co-Creation</title>
      <link>https://blog.libove.org/posts/2018-01-30-human-machine-co-creation/</link>
      <pubDate>Tue, 30 Jan 2018 20:59:21 +0000</pubDate>
      <description><![CDATA[<p><img src="media/14092973648_9e323a2cbc_z.jpg" alt="Human Machine Co Creation"></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2018-01-07-weizen-kastenbrot-mit-saaten/</guid>
      <title>Weizen Kastenbrot mit Saaten</title>
      <link>https://blog.libove.org/posts/2018-01-07-weizen-kastenbrot-mit-saaten/</link>
      <pubDate>Sun, 07 Jan 2018 14:47:26 +0000</pubDate>
      <description><![CDATA[<p><img src="media/IMG_20180107_152541_2_low.jpg" alt="Weizen Kastenbrot mit Saaten"></p>
<p>Ein einfaches Rezept für ein Kastenbrot aus Weizenmehl. Die Zusammensetzung der Saaten kann beliebig angepasst werden.</p>
<p> </p>
<h4>Vorteig:</h4>
<ul>
<li><strong>250g</strong> Weizenmehl 550</li>
<li><strong>250g</strong> Wasser</li>
<li><strong>60g</strong> Anstellgut (TA200)</li>
</ul>
<h4>Saaten:</h4>
<ul>
<li><strong>80g</strong> Sonnenblumkerne</li>
<li><strong>40g</strong> Leinensamen</li>
<li><strong>40g</strong> Haferflocken</li>
<li><strong>160g</strong> heißes Wasser (ca. 90° C)</li>
</ul>
<h4>Hauptteig</h4>
<ul>
<li>Vorteig</li>
<li>Saaten</li>
<li><strong>200g</strong> Weizenmehl 550</li>
<li><strong>15g</strong> Salz</li>
<li><strong>5g</strong> Hefe</li>
</ul>
<p>Zutaten des Vorteigs gut vermengen und 12 Stunden bei Raumtemperatur gehen lassen</p>
<p>Saaten mischen und mit heißem Wasser übergießen. Mit eine Klarsichtfolie abdecken und ca. 1 Stunde ziehen lassen.</p>
<p>Alle Zutaten des Hauptteigs vermengen und per Hand zu einem weichen, leicht klebrigen Teig kneten.</p>
<p>Teig 90 Minuten gehen lassen und alle 30 Minuten in der Schüssel falten.</p>
<p>Teig auf einer bemehlten Arbeitsfläche formen und direkt in die Kastenform geben.</p>
<p>Für weitere 2 Stunden gehen lassen, bis sich das Volumen des Teigs etwa verdoppelt hat.</p>
<p>Vor dem backen den Brotteig längs einschneiden und gut befeuchten (z.B. mit einer Sprühflasche)</p>
<p>Den Ofen auf 250° C vorheizen und das Brot für 10 Minuten backen. Danach die Temperatur auf 200° C reduzieren und für weitere 35 Minuten backen. Das Brot aus dem Kasten nehmen und für weitere 10 Minuten auf einem Rost backen.</p>
<p> 
<img src="media/IMG_20180107_162458_2_low.jpg" alt="Weizen Kastenbrot mit Saaten Anschnitt"></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2017-11-25-archive-master-thesis-notes/</guid>
      <title>Archive: Master Thesis Notes</title>
      <link>https://blog.libove.org/posts/2017-11-25-archive-master-thesis-notes/</link>
      <pubDate>Sat, 25 Nov 2017 20:00:13 +0000</pubDate>
      <description><![CDATA[<p><img src="media/IMG_20171125_204056.jpg" alt="">
<img src="media/IMG_20171125_204102.jpg" alt="">
<img src="media/IMG_20171125_204155.jpg" alt="">
<img src="media/IMG_20171125_204119.jpg" alt="">
<img src="media/IMG_20171125_204136.jpg" alt="">
<img src="media/IMG_20171125_204149.jpg" alt="">
<img src="media/IMG_20171125_204112.jpg" alt=""></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2017-06-20-blog-running-on-new-server/</guid>
      <title>Blog Running On New Server</title>
      <link>https://blog.libove.org/posts/2017-06-20-blog-running-on-new-server/</link>
      <pubDate>Tue, 20 Jun 2017 16:01:33 +0000</pubDate>
      <description><![CDATA[<p>I transferred the blog to a new server. Hopefully everything still works.</p>
<p><strong>Update:</strong></p>
<p><a href="https://pathfinder.libove.org">https://pathfinder.libove.org</a> running on new server</p>
<p><a href="https://tools.libove.org">https://tools.libove.org</a> running on new server</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2017-06-15-django-formview-lifecycle/</guid>
      <title>Django FormView Lifecycle</title>
      <link>https://blog.libove.org/posts/2017-06-15-django-formview-lifecycle/</link>
      <pubDate>Thu, 15 Jun 2017 16:53:33 +0000</pubDate>
      <description><![CDATA[<p><img src="media/FormView-1.svg" alt=""></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2017-04-25-how-to-create-pixel-night-sky/</guid>
      <title>How to: Create Pixel Night Sky</title>
      <link>https://blog.libove.org/posts/2017-04-25-how-to-create-pixel-night-sky/</link>
      <pubDate>Tue, 25 Apr 2017 06:57:19 +0000</pubDate>
      <description><![CDATA[<p>I've used this method to create the night sky of my <a href="https://blog.libove.org/posts/2017-04-24-ludum-dare-38-little-sky-farm">Ludum Dare 38 Game</a>. It's an easy way to create the stars of the night sky in few minutes.</p>
<p>Start with two layers, one for the dark sky and one for the stars. The dark sky is just uses a dark, blue color. The star layer contains a mixture of light colors. It is up to you how you create this layer. I painted the layer randomly with 3 colors and used the <strong>Smudge Tool</strong> to mix them. Make sure the star layer has an alpha layer.</p>
<p><a href="/media/2_a.png"><img src="/thumbnail/2_a.png" alt="Initial Layers for Pixel Stars"></a></p>
<p>To convert the randomly colored layer into stars the <strong>RGB Noise Filter</strong> is used, which can be found under <strong>Filters</strong> → <strong>Noise</strong> → <strong>RGB Noise …</strong>. Set up the filter as shown below. The Red, Green and Blue Noise is completely disabled and only the Alpha Noise is applied. If you do not see the Alpha slider of the filter make sure that the layer has an <strong>Alpha Channel</strong>.</p>
<p><a href="/media/NOISE.png"><img src="/thumbnail/NOISE.png" alt=""></a></p>
<p>After applying the filter for the first time the result does not look very promising. If you keep applying the filter repeatedly (using the <strong>Ctrl+F</strong> shortcut) the remaining color will get sparser and sparser. Keep doing this until you are satisfied with the density of the stars. The results should look like the example below.</p>
<p> </p>
<p><a href="/media/5.png"><img src="/thumbnail/5.png" alt="Finished Pixel Star Night"></a></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2017-04-24-ludum-dare-38-little-sky-farm/</guid>
      <title>Ludum Dare 38: Little Sky Farm</title>
      <link>https://blog.libove.org/posts/2017-04-24-ludum-dare-38-little-sky-farm/</link>
      <pubDate>Mon, 24 Apr 2017 19:17:03 +0000</pubDate>
      <description><![CDATA[<h1><a href="https://h4kor.itch.io/small-sky-farm">Play Little Sky Farm</a></h1>
<p><a href="/media/1.png"><img src="/thumbnail/1.png" alt="Little Sky Farm"></a></p>
<h2>Story</h2>
<p>You want to go the final step with your loved one but damn it is expensive. Living on a small floating island there isn't much to earn money with, but you have to find a way before it is too late. Farm the little land you have to rack up the money you need.</p>
<h2>Game Mechanics</h2>
<p>The worth of objects changes with time. Most objects get more valuable over time, but only to a certain point. Once they exceeded this point the value deteriorates until they are completely worthless.</p>
<p>It is your job to find out how long processes have to run for an optimal outcome.</p>
<p>This requires keen observation and managing your time between different tasks.</p>
<h2>Controls</h2>
<ul>
<li><strong>WASD</strong> and <strong>Arrow-Keys</strong>: Walk and Jump</li>
<li><strong>Spacebar:</strong> Use selected tool</li>
<li><strong>Mouse</strong>: Control build menu</li>
<li><strong>1-8</strong>: Change Tool</li>
</ul>
<h3>Tools</h3>
<ul>
<li><strong>Hammer</strong>: Interact with most objects</li>
<li><strong>Wood</strong>: Plant trees</li>
<li><strong>Wheat</strong>: Plant Wheat</li>
<li><strong>Other</strong>: there might be hidden functionalities</li>
</ul>
<h2>Credits</h2>
<h3>Third Party Tools/Assets</h3>
<ul>
<li><a href="https://godotengine.org/">Godot Engine</a></li>
<li><a href="https://www.gimp.org/">Gimp</a></li>
<li><a href="https://www.reddit.com/r/gamedev/comments/66v29j/my_1000_free_instrumental_background_music_tracks/">Music From Antti Luode</a></li>
</ul>
<h3>Sounds/Assets</h3>
<ul>
<li><a href="http://www.freesound.org/people/gruenebanane/sounds/241813/">http://www.freesound.org/people/gruenebanane/sounds/241813/</a></li>
<li><a href="http://www.freesound.org/people/acebrian/sounds/380471/">http://www.freesound.org/people/acebrian/sounds/380471/</a></li>
<li><a href="http://www.freesound.org/people/ryanharding95/sounds/272442/">http://www.freesound.org/people/ryanharding95/sounds/272442/</a></li>
<li><a href="http://www.freesound.org/people/ryanconway/sounds/240801/">http://www.freesound.org/people/ryanconway/sounds/240801/</a></li>
<li><a href="http://www.freesound.org/people/deleted_user_877451/sounds/66113/">http://www.freesound.org/people/deleted_user_877451/sounds/66113/</a></li>
<li><a href="http://www.freesound.org/people/vumseplutten1709/sounds/208829/">http://www.freesound.org/people/vumseplutten1709/sounds/208829/</a></li>
<li><a href="http://www.freesound.org/people/jorge0000/sounds/361053/">http://www.freesound.org/people/jorge0000/sounds/361053/</a></li>
</ul>
<p><a href="/media/3.png"><img src="/thumbnail/3.png" alt="Little Sky Factory"></a></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2017-03-07-pizza-timelapse/</guid>
      <title>Pizza Timelapse</title>
      <link>https://blog.libove.org/posts/2017-03-07-pizza-timelapse/</link>
      <pubDate>Tue, 07 Mar 2017 15:50:16 +0000</pubDate>
      <description><![CDATA[<p>{{&lt; youtube SMxpO0z-Vz4 &gt;}}</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2017-02-10-weizensauerteigbrot/</guid>
      <title>Weizensauerteigbrot</title>
      <link>https://blog.libove.org/posts/2017-02-10-weizensauerteigbrot/</link>
      <pubDate>Fri, 10 Feb 2017 18:32:19 +0000</pubDate>
      <description><![CDATA[<p><img src="media/weizen2.jpg" alt="Weizensauerteigbrot"></p>
<p>Brot für das Wochenende musste her. Daher habe ich einen Teig angesetzt und mich von den Mengen am <a href="https://www.ploetzblog.de/2016/01/02/neujahrsbrot/">Neujahrsbrot</a> orientiert. Ich habe dem Hauptteig noch etwas Hefe zugesetzt, da ich meinem Sauerteig noch nicht ganz vertraue. Herausgekommen ist ein schönes Brot mit einer großporigen Krume.</p>
<p><strong>Sauerteig</strong></p>
<ul>
<li>105 g Weizenmehl 550</li>
<li>105 g Wasser (50°C)</li>
<li>7 g Anstellgut (mild geführt)</li>
</ul>
<p><strong>Autolyseteig</strong></p>
<ul>
<li>560 g Weizenmehl 550</li>
<li>355 g Wasser (55°C)</li>
</ul>
<p><strong>Hauptteig</strong></p>
<ul>
<li>Sauerteig</li>
<li>Autolyseteig</li>
<li>30 g Weizenmehl 550</li>
<li>16 g Salz</li>
<li>70 g Wasser (30°C)</li>
<li>0,3g Trokenhefe</li>
</ul>
<p>Den Sauerteig ansetzen und ca. 12 Stunden bei Raumtemperatur reifen lassen.</p>
<p>Autolyseteig gut vermischen und 1 Stunde stehenlassen.</p>
<p>Alle Zutaten von Hand vermischen und 3 Stunden reifen lassen. Die ersten 2 Stunden 3 mal in der Schüssel falten.</p>
<p>Teig auf eine bemehlte Arbeitsfläche geben, in zwei Portionen teilen und schonend lang wirken.</p>
<p>1 Stunde im Gärkorb mit Schluss nach oben garen lassen.</p>
<p>Bei 250° C fallend auf 150° C mit Dampf für 30 Minuten backen.</p>
<p><img src="media/weizen1.jpg" alt="Weizensauerteigbrot"></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2017-02-01-nusdorfer-landbrot/</guid>
      <title>Nußdorfer Landbrot</title>
      <link>https://blog.libove.org/posts/2017-02-01-nusdorfer-landbrot/</link>
      <pubDate>Wed, 01 Feb 2017 20:32:59 +0000</pubDate>
      <description><![CDATA[<p><img src="media/IMG_20170201_181454-2.jpg" alt="Nußdorfer Landbrot"></p>
<p>Ich habe nun schon zum zweiten Mal das Nußdorfer Landbrot von <a href="https://www.ploetzblog.de/2012/02/27/nussdorfer-landbrot-mild/">Plötzblog</a> gebacken und bin sehr zufrieden mit dem Rezept. Der Teig lässt sich etwas schwer per Hand kneten, was an dem Roggenanteil liegen dürfte.</p>
<p><strong>Weizensauerteig</strong></p>
<ul>
<li>150 g Weizenmehl 1050</li>
<li>110 g Wasser</li>
<li>15 g Anstellgut</li>
</ul>
<p><strong>Vorteig</strong></p>
<ul>
<li>100 g Weizenmehl 1050</li>
<li>100 g Wasser</li>
<li>0,1 g Frischhefe</li>
</ul>
<p><strong>Hauptteig</strong></p>
<ul>
<li>Sauerteig</li>
<li>Vorteig</li>
<li>100 g Weizenmehl 1050</li>
<li>200 g Roggenmehl 1150</li>
<li>150 g Wasser</li>
<li>12 g Salz</li>
</ul>
<p>Die Vorteig in ansetzen und etwa 20 Stunden bei Raumtemperatur reifen lassen.</p>
<p>Alle Zutaten vermengen und ca. 10 Minuten von Hand kneten. Der Teig sollte nur noch leicht an der Hand kleben.</p>
<p>2 Stunden zur Gare stellen und nach einer Stunde falten.</p>
<p>Teig zu einem länglichen Laib formen und in einen gut bemehlten Gärkorb geben. Ich habe den Teig leicht über eine bemehlte Arbeitsfläche gerollt um ihn in Form zu bringen.</p>
<p>Etwa 2 Stunden Gare im Gärkorb.</p>
<p>Statt das Brot zu stippen, habe ich es quer eingeschnitten.</p>
<p>Bei 250° C fallend auf 150° C mit Dampf für 40 Minuten backen. (Achtung mein Ofen verbrennt schnell das Brot, deswegen Temperaturen und Backzeit eher nach oben anpassen)</p>
<p> </p>
<p><img src="media/IMG_20170201_210836-2.jpg" alt="Nußdorfer Landbrot"></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2017-01-17-kreolischer-eintopf/</guid>
      <title>Kreolischer Eintopf</title>
      <link>https://blog.libove.org/posts/2017-01-17-kreolischer-eintopf/</link>
      <pubDate>Tue, 17 Jan 2017 16:23:01 +0000</pubDate>
      <description><![CDATA[<p><img src="media/img_0002-1-1024x683.jpg" alt="Kreolischer Eintopf"></p>
<p>Das Rezept ist inspiriert von <a href="http://www.chefkoch.de/rezepte/998561205222520/Kreolisches-Rindfleisch-Curry-mit-Gemuese.html">Kreolisches Rindfleisch – Curry mit Gemüse</a>. Der Eintopf ist ideal für die kalten Jahreszeiten und lässt sich schnell zubereiten.</p>
<p><strong>Zutaten:</strong></p>
<ul>
<li>500 g Schweine-Gulash</li>
<li>500 g Karotten</li>
<li>750 g Kartoffeln</li>
<li>500 ml Gemüsebrühe</li>
<li>3 Tomaten</li>
<li>3 Zwiebeln</li>
<li>4 Knoblauchzehen</li>
<li>1 Stück Ingwer</li>
<li>1 TL Kurkuma</li>
<li>1 TL Thymian</li>
<li>Salz und Pfeffer</li>
<li>Öl zum anbraten</li>
</ul>
<p><img src="media/img_0001-2-1024x683.jpg" alt="Kreolischer Eintopf Zutaten"></p>
<p>Karotten und Kartoffeln schälen und ca 1 cm dicke Würfel schneiden. Zwiebeln fein würfeln, Knoblauch fein hacken und Ingwer zerreiben oder ebenfalls fein hacken. Tomaten würfeln.</p>
<p>Fleisch anbraten und mit Salz und Pfeffer würzen. Zwiebeln hinzugeben und mit anbraten. Tomaten, Ingwer und Knoblauch hinzugeben und kurz mit anbraten. Mit der Brühe ablöschen und Kartoffeln, Karotten und Gewürze hinzugeben.</p>
<p>Etwa 30 Minuten kochen bis Kartoffeln und Karotten gar sind.</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2017-01-16-roggenvollkornbrot/</guid>
      <title>Roggenvollkornbrot</title>
      <link>https://blog.libove.org/posts/2017-01-16-roggenvollkornbrot/</link>
      <pubDate>Mon, 16 Jan 2017 08:34:18 +0000</pubDate>
      <description><![CDATA[<p><img src="media/roggen2-1024x556.jpg" alt="Roggenvollkornbrot"></p>
<p>Nach meinen ersten Versuchen mit Weizensauerteig Broten, wollte ich eine anderes Mehl versuchen und entschied mich für das <a href="https://www.ploetzblog.de/2015/10/17/roggenvollkornbrot-nach-trine-hahnemann/">Roggenvollkornbrot</a>. Eigentlich hatte ich mir vorgenommen die Teigmenge zu halbieren, dies aber schon beim ansetzen des 1. Sauerteiges vergessen. Deswegen sind es direkt 3 Brote geworden. Das Rezept habe ich nur leicht verändert. Ich habe Weizen Anstellgut benutzt, da ich kein neues für diesen Versuch herstellen wollte. Der fertige Teig war sehr klebrig und ließ sich kaum verarbeiten, weswegen ich beim nächsten mal das Wasser des Hauptteigs <a href="https://www.ploetzblog.de/2015/10/17/roggenvollkornbrot-nach-trine-hahnemann/#comment-61393">weglassen</a> würde. Die Backzeit habe ich auf 40-45 Minuten reduziert, da ich kleiner Brote gebacken habe. Außerdem hab ich fallend von 250° C auf 180° C gebacken, was meinem Backofen geschuldet ist.</p>
<p>Das Brot schmeckt leicht säuerlich und hat eine feine Porung.</p>
<p>Das Rezept gefällt mir sehr gut, da es sich gut in den Alltag integrieren lässt und keinen Dampf erfordert (den ich in meinem Ofen nur schlecht aufbauen kann). Ich habe den ersten Sauerteig abends angesetzt, den zweiten direkt am morgen zubereitet und konnte am Abend das Brot backen.</p>
<p> </p>
<p><strong>Erster Sauerteig</strong></p>
<ul>
<li><strong>170 g</strong> Roggenvollkornmehl</li>
<li><strong>170 g</strong> Wasser (warm)</li>
<li><strong>17 g</strong> Anstellgut (Weizen)</li>
</ul>
<p><strong>Zweiter Sauerteig</strong></p>
<ul>
<li>Erster Sauerteig</li>
<li><strong>520 g</strong> Roggenvollkornmehl</li>
<li><strong>520 g</strong> Wasser (warm)</li>
<li><strong>18 g</strong> Salz</li>
</ul>
<p><strong>Hauptteig</strong></p>
<ul>
<li>Zweiter Sauerteig</li>
<li><strong>200 g</strong> Roggenvollkornmehl</li>
<li><strong>110 g</strong> Wasser (warm)</li>
</ul>
<p>Ersten Sauerteig vermischen und 12 Stunden ruhen lassen.</p>
<p>Zweiten Sauerteig vermischen und ~9 Stunden ruhen lassen.</p>
<p>Hauptteig vermischen, Wasser langsam einarbeiten bis Konsistenz stimmt, Laib formen und 90 Minuten im Gärkorb ruhen lassen.</p>
<p>Bei 250° C fallend auf 180° C backen. 500 g Brote für 40 Minuten backen, bei größeren Laiben die Backzeit anpassen.</p>
<p> </p>
<p><img src="media/roggen1-1024x664.jpg" alt="Roggenvollkornbrot Anschnitt"></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2017-01-11-erstes-erfolgreiches-sauerteig-brot/</guid>
      <title>Erstes (erfolgreiches) Sauerteig-Brot</title>
      <link>https://blog.libove.org/posts/2017-01-11-erstes-erfolgreiches-sauerteig-brot/</link>
      <pubDate>Wed, 11 Jan 2017 16:42:57 +0000</pubDate>
      <description><![CDATA[<p>Ich habe versucht das <a href="https://www.ploetzblog.de/2012/08/15/bordelaise/">Bordelaise</a> von Plötzblog nach zu backen. Mein erster Versuch ist kläglich gescheitert. Das Brot ist nicht aufgegangen und hatte lediglich 2 riesige Blasen unter der Kruste. Bei meinem zweiten Versuch habe ich das Rezept leicht abgeändert und das Brot ist gut aufgegangen. Leider ist das Brot nicht am Einschnitt aufgegangen, sondern an der Unterseite eingerissen.</p>
<p><img src="media/img_0001-1.jpg" alt=""></p>
<p><strong>Sauerteig</strong> (unverändert)<strong>:</strong></p>
<ul>
<li><strong>65 g</strong> Weizenmehl 550</li>
<li><strong>80 g</strong> Wasser</li>
<li><strong>6 g</strong> Anstellgut</li>
</ul>
<p><strong>Hauptteig</strong></p>
<ul>
<li>Sauerteig</li>
<li><strong>335 g</strong> Weizenmehl 550</li>
<li><strong>40 g</strong> Weizenvollkornmehl</li>
<li><strong>200 g</strong> Wasser</li>
<li><strong>9 g</strong> Salz</li>
</ul>
<p>Der Sauerteig hat 20 Stunden bei Raumtemperatur geruht.</p>
<p>Beim Hauptteig habe ich 10g Wasser weniger benutzt als im Orginalrezept. Außerdem habe ich die 30 g Roggenvollkornmehl gegen zusätzliche 30 g Weizenvollkornmehl ersetzt. Der Hauptteig ohne Salz wurde von Hand vermischt und 30 Minuten ruhen gelassen. Nach der Ruhe wurde das Salz hinzu gegeben und der Teig für 5 Minuten von Hand geknetet.</p>
<p>Die restlichen Garzeiten blieben unverändert, jedoch nur bei Raumtemperatur durchgeführt.</p>
<p>1 Stunde Gare, dann falten.</p>
<p>2 Stunde Gare, dann rund wirken.</p>
<p>15 Minuten Ruhe, dann Laib formen.</p>
<p>2,5 Stunden Gare im Gärkorb mit Schluss nach oben.</p>
<p>Zum Backen den Ofen eine halbe Stunde auf 250° C vorheizen. Für den Dampf habe ich eine Auflaufform mit Wasser in den Ofen gestellt. Das Brot wurde für 40 Minuten bei 250° C fallend auf 200° C gebacken. Nach 40 Minuten habe ich die Wasserschale entfernt und das Brot weitere 8 Minuten gebacken.</p>
<p>Geplante Änderungen für den nächsten Versuch:</p>
<ul>
<li>Einschnitt ändern (tiefer oder mehrere Schnitte?)</li>
<li>Mehr Dampf. Wasser hatte nur leicht geköchelt.</li>
<li>Temperatur stärker abfallen lassen. Brot sehr dunkel geworden.</li>
</ul>
<p> </p>
<p><img src="media/img_0001.jpg" alt="Brot ist unten gerissen"></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2017-01-11-loss-of-attribution/</guid>
      <title>Loss of Attribution</title>
      <link>https://blog.libove.org/posts/2017-01-11-loss-of-attribution/</link>
      <pubDate>Wed, 11 Jan 2017 16:01:49 +0000</pubDate>
      <description><![CDATA[<p>The digitalisation of the world and the Internet changed the rules in many, if not all, fields. One major change I just realised is the loss of attribution. I often made these errors when in contact with opinions that I didn't share. When it came to my personal views and groups I feel associated to, I felt mistreated or misunderstood if people made general claims about such group.</p>
<p>Before the internet it was relatively easy to attribute an action or statement to an organisation, such as a political party or a state. Actions were attributable because evidence was impossible, or at least very hard, to forge (without leaving a trace). Statements made by an individual could be traced back as opinion were exchanged by direct contact.</p>
<p>This changed in the digital world. Data can be manipulated without leaving traces and left traces are hard to find and could also be created on purpose.</p>
<p>For this reason actions in the internet, such as hacking an organisation, can't be attributed to someone only based on the traces left in the process. Hard, physical proof is needed to do such an attribution.</p>
<p>Hacks are generally attributed to the Russian government at the moment. This attribution explains (some) features of found malware and traces. However a skilled hacker could have avoided such traces or planted them on purpose.</p>
<p>Statements made by an individual are often labeled as being left, right or liberal believes, but are just the interpretations and thoughts of a single person. Even if this person associates itself with a specific group doesn't make his statements general believes of such group. The association of an individual with a group got more fluid in the internet. One only has to claim being associated to be seen as voice of such group.</p>
<p>This error, where association is confused with attribution, can often be seen in today's debates. For example, feminists are viewed as crazy women who want to see men suffer because a few individuals with extreme viewpoints identify themselves as feminists. The political right is often set on one level with Nazis because, again, few individuals who call themselves &quot;right&quot; spread hatred against foreigners. The political left is confused with people that despise all authority or want to recreate the communism of the cold war.</p>
<p>In conclusion, our old techniques of attribution don't work any longer in the digital world. Actions can't be attributed and statements made by individuals only show their views, not general views of a group. This makes policital and social debates more complex, as organisations (movements/parties) can't be described by pointing on individual events or statements. Other techniques are required to pinpoint the general believes of a group.</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2016-12-19-pizza-sauce-journal-exact-measurements/</guid>
      <title>Pizza Sauce Journal: Exact Measurements</title>
      <link>https://blog.libove.org/posts/2016-12-19-pizza-sauce-journal-exact-measurements/</link>
      <pubDate>Mon, 19 Dec 2016 12:57:16 +0000</pubDate>
      <description><![CDATA[<h2>Ingredients:</h2>
<ul>
<li><strong>500 g</strong> Chopped Tomatoes</li>
<li><strong>100 ml</strong> Water</li>
<li><strong>1</strong> small Onion</li>
<li><strong>1</strong> Garlic Clove</li>
<li><strong>1 Tsp</strong> Olive Oil</li>
<li><strong>1/2 tsp</strong> Pepper</li>
<li><strong>1 tsp</strong> Salt</li>
<li><strong>1/4 tsp</strong> dried Chilis (Habaneros)</li>
<li><strong>1 tsp</strong> Marjoram</li>
<li><strong>1 tsp</strong> Oregano</li>
</ul>
<h2>Preparation:</h2>
<p>Sear chopped onions and garlic for ~2 minutes in olive oil, until the garlic starts to turn brown.</p>
<p>Add tomatoes and water.</p>
<p>Let it simmer for 5 minutes and add all spices.</p>
<p>Mix everything up and let it simmer for ~30 minutes with a half closed lid.</p>
<p>Blend everything to a smooth sauce.</p>
<h2>Conclusion:</h2>
<p>Taste dominated by the hotness of the chili. Marjoram and Oregano barely noticeable. Pizza taste dictated by toppings and dough, sauce stays in background.
<img src="media/img_0001-1024x766.jpg" alt="Sauce in Jar">
<img src="media/img_0002-1024x768.jpg" alt="Sauce on Pizza"></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2016-12-16-free-tileable-textures/</guid>
      <title>Free Tileable Textures</title>
      <link>https://blog.libove.org/posts/2016-12-16-free-tileable-textures/</link>
      <pubDate>Fri, 16 Dec 2016 15:37:04 +0000</pubDate>
      <description><![CDATA[<p>The texture are tileable in X and Y direction and have a resolution of 2048&amp;#215;2048.</p>
<p>All textures are licensed under <a href="https://creativecommons.org/licenses/by/4.0/legalcode">CC-BY 4.0</a></p>
<p><img src="media/2048-1024x1024.png" alt="Tileable Concrete Texture">
<img src="media/2048_KvbeBn4-1024x1024.png" alt="Tileable Driveway Texture">
<img src="media/2048_tIldLCS-1024x1024.png" alt="Tileable Dirt Texture">
<img src="media/2048_yGKEfp6-1024x1024.png" alt="Tileable Gravel Texture">
<img src="media/fur-1024x1024.png" alt="Tileable Fur Texture">
<img src="media/gravel2048-1024x1024.png" alt="Tileable Gravel Texture"></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2016-12-09-simple-pizza-sauce/</guid>
      <title>Simple Pizza Sauce</title>
      <link>https://blog.libove.org/posts/2016-12-09-simple-pizza-sauce/</link>
      <pubDate>Fri, 09 Dec 2016 14:56:06 +0000</pubDate>
      <description><![CDATA[<p><img src="media/DSC02284-1024x683.jpg" alt="Simple Pizza Sauce"></p>
<p><strong>Ingredients for Simple Pizza Sauce for ~4 Pizzas:</strong></p>
<ul>
<li><strong>1 Packet/Can</strong> Tomato Pieces</li>
<li>**1 Tablespoon **Olive Oil</li>
<li><strong>1 Teaspoon</strong> Salt</li>
<li><strong>1-2 Teaspoon</strong> Oregano</li>
<li><strong>1-2</strong> chopped Garlic Gloves</li>
<li>Pepper</li>
<li><strong>optionally</strong> Chili Powder or chopped Chilies</li>
</ul>
<p>Add all ingredients into a pot and heat it until the sauce starts to cook. Leave the sauce simmering for approx. 30 minutes with the cover on but not closing the pot completely. The sauce is ready once most of the water is evaporated. If you don't like your sauce chunky use a mixer to create a smooth sauce.</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2016-12-09-simple-pizza-dough/</guid>
      <title>Simple Pizza Dough</title>
      <link>https://blog.libove.org/posts/2016-12-09-simple-pizza-dough/</link>
      <pubDate>Fri, 09 Dec 2016 14:33:28 +0000</pubDate>
      <description><![CDATA[<p><img src="media/DSC02278-1024x683.jpg" alt=""></p>
<p><strong>Ingredients for 1 Pizza:</strong></p>
<ul>
<li><strong>175 g</strong> Flour</li>
<li><strong>100 ml</strong> Water</li>
<li><strong>¼</strong> Yeast Cube or  <strong>½</strong> Packet Dry Yeast</li>
<li><strong>1 Tablespoon</strong> Olive Oil</li>
<li><strong>1/2 Teaspoon</strong> Salt</li>
</ul>
<p>Mix flour and salt and dissolve yeast in water. Add water and oil to the flour and knead it until the dough is smooth. Let the dough rest for at least 30 minutes, better overnight in the refrigerator.</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2016-11-01-pathfinder-character-gnome-illusionist/</guid>
      <title>Pathfinder Character: Gnome Illusionist</title>
      <link>https://blog.libove.org/posts/2016-11-01-pathfinder-character-gnome-illusionist/</link>
      <pubDate>Tue, 01 Nov 2016 21:56:05 +0000</pubDate>
      <description><![CDATA[<p>This character is the result of severals campaigns. He started at level 6 and was recently retired at level 11, when we decided to start with new characters and new adventures. As a result his equipment is a bit arbitrary and worth more than allowed when constructing a new level 11 character. He has the craft wondrous items feat, which would allow to create his equipment for a lower price.</p>
<p><strong>Name:</strong> Blimmderox Rattlezak</p>
<p><strong>Race:</strong> Gnome</p>
<p><strong>Class:</strong> Wizard/11</p>
<h2>Ability Scores {<span class="hashtag"><a class="p-category" href="/tags/ability-scores/">#ability-scores</a></span>}</h2>
<p><strong>Raw:</strong> <strong>STR:</strong> 5 <strong>DEX:</strong> 14 <strong>CON:</strong> 15 <strong>INT:</strong> 20 <strong>WIS:</strong> 8 <strong>CHA:</strong> 13</p>
<p><strong>Equipped:</strong> <strong>STR:</strong> 5 <strong>DEX:</strong> 16 <strong>CON:</strong> 15 <strong>INT:</strong> 26 <strong>WIS:</strong> 8 <strong>CHA:</strong> 13</p>
<p>(<strong>Point Buy 20:</strong> <strong>STR:</strong> 7 <strong>DEX:</strong> 14 <strong>CON:</strong> 13 <strong>INT:</strong> 18 <strong>WIS:</strong> 8 <strong>CHA:</strong> 11, <strong>+2 CON/CHA -2 STR</strong> Race Bonus, <strong>+2 INT</strong> from level 4+8)</p>
<h2><strong>Combat</strong></h2>
<p><strong>Hit Points:</strong> 11D6 + 33</p>
<p><strong>Initiative:</strong> 11</p>
<p><strong>Reflex:</strong> 6 <strong>Fortitude:</strong> 5 <strong>Willpower:</strong> 6</p>
<p><strong>Armor Class:</strong> 20 <strong>Touch:</strong> 16 <strong>Flat-footed:</strong> 17</p>
<p><strong>CMB:</strong> 1 <strong>CMD:</strong> 14</p>
<p><strong>Quaterstaff:</strong> Attacks: +2 Damage: 1d4-3 Crit: 20/x2</p>
<p><strong>Ranged Attacks:</strong> Attacks: +8</p>
<h2><strong>Feats</strong></h2>
<p>(6 regular + 2 wizard bonus + Scribe Scroll)</p>
<ul>
<li><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/feats.html#scribe-scroll">Scribe Scroll</a> – Create magic scrolls</li>
<li><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/feats.html#craft-wondrous-item">Craft Wondrous Item</a> – Create magic wondrous items</li>
<li><a href="http://www.d20pfsrd.com/feats/general-feats/effortless-trickery">Effortless Trickery</a> – You can maintain concentration on one spell of the illusion school as a swift action.</li>
<li><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/feats.html#spell-focus">Spell Focus</a> (Illusion) – +1 bonus on save DCs for one school</li>
<li><a href="http://www.d20pfsrd.com/feats/metamagic-feats/threatening-illusion-metamagic">Threatening Illusion</a> – A threatening illusion spell causes one target to believe your illusion is a threat.</li>
<li><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/feats.html#greater-spell-focus">Greater Spell Focus</a> (Illusion) – +1 bonus on save DCs for one school</li>
<li><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/feats.html#improved-initiative">Improved Initiative</a> – +4 bonus on initiative checks</li>
<li><a href="http://paizo.com/pathfinderRPG/prd/advancedPlayersGuide/advancedFeats.html#dazing-spell">Dazing Spell</a> –  Daze creature with spell damage</li>
<li><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/feats.html#improved-familiar">Improved Familiar</a> – Gain a more powerful familiar</li>
</ul>
<h2><strong>Traits</strong></h2>
<ul>
<li><strong>Reactionary:</strong> You were bullied often as a child, but never quite developed an offensive response. Instead, you became adept at anticipating sudden attacks and reacting to danger quickly. You gain a +2 trait bonus on Initiative checks.</li>
<li><strong>Fidgety:</strong> +2 Initiative (can't find the English name, but it's from Gnomes of Golarion)</li>
</ul>
<h2>Illusion School {<span class="hashtag"><a class="p-category" href="/tags/illusion-school/">#illusion-school</a></span>}</h2>
<p><strong>Forbidden Schools:</strong> Necromancy, Divination</p>
<p><strong><em>Extended Illusions</em>:</strong> Any illusion spell you cast with a duration of &quot;concentration&quot; lasts a number of additional rounds equal to 1/2 your wizard level after you stop maintaining concentration (minimum +1 round). At 20th level, you can make one illusion spell with a duration of &quot;concentration&quot; become permanent. You can have no more than one illusion made permanent in this way at one time. If you designate another illusion as permanent, the previous permanent illusion ends.</p>
<p><strong><em>Blinding Ray</em>:</strong> As a standard action you can fire a shimmering ray at any foe within 30 feet as a ranged touch attack. The ray causes creatures to be blindedfor 1 round. Creatures with more Hit Dice than your wizard level are dazzled for 1 round instead. You can use this ability a number of times per day equal to 3 + your Intelligence modifier.</p>
<p><strong><em>Invisibility Field</em>:</strong> At 8th level, you can make yourself invisible as a swift action for a number of rounds per day equal to your wizard level. These rounds do not need to be consecutive. This otherwise functions as <em>greater invisibility.</em></p>
<h2>Gnome Traits</h2>
<ul>
<li><strong>Small</strong>: Gnomes are Small creatures and gain a +1 size bonus to their AC, a +1 size bonus on attack rolls, a –1 penalty to their Combat Maneuver Bonus and Combat Maneuver Defense, and a +4 size bonus on Stealth checks.</li>
<li><strong>Slow Speed</strong>: Gnomes have a base speed of 20 feet.</li>
<li><strong>Low-Light Vision</strong>: Gnomes can see twice as far as humans in conditions of dim light.</li>
<li><strong>Illusion Resistance</strong>: Gnomes gain a +2 racial saving throw bonus against illusion spells and effects.</li>
<li><strong>Gnome Magic</strong>: Gnomes add +1 to the DC of any saving throws against illusion spells that they cast. Gnomes with Charisma scores of 11 or higher also gain the following spell-like abilities: 1/day—<em><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/dancingLights.html#dancing-lights">dancing lights</a>, <a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/ghostSound.html#ghost-sound">ghost sound</a>, <a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/prestidigitation.html#prestidigitation">prestidigitation</a>,</em> and <em><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/speakWithAnimals.html#speak-with-animals">speak with animals</a></em>. The caster level for these effects is equal to the gnome's level. The DC for these spells is equal to 10 + the spell's level + the gnome's Charisma modifier.</li>
<li><strong>Gift of Tongues</strong>: Gnomes love languages and learning about those they meet. Gnomes with this racial trait gain a +1 bonus on Bluff and Diplomacy checks, and they learn one additional language every time they put a rank in the Linguistics skill. This racial trait replaces defensive training and hatred.</li>
<li><strong>Obsessive</strong>: Gnomes receive a +2 racial bonus on a Craft or Profession skill of their choice.</li>
</ul>
<h2><strong>Equipment</strong></h2>
<p>(82000 GP at level 10, crafting allows to spend up to 164000)</p>
<p>As this character is the result of multiple campaigns. It acquired gear worth <strong>106800 gp</strong>, mainly by crafting the more expensive items.</p>
<ul>
<li><strong>Headband of vast intelligence +6</strong> (36000) – +6 Intelligence</li>
<li><strong>Hat of disguise</strong> (1800) – This apparently normal hat allows its wearer to alter her appearance as with a <a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/disguiseSelf.html#disguise-self">disguise self</a> spell.</li>
<li><strong>Cloak of displacement, minor</strong> (24000) – This displacement works similar to the <a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/blur.html#blur">blur</a> spell, granting a 20% miss chance on attacks against the wearer. It functions continually.</li>
<li><strong>Amulet of natural armor +1</strong> (2000) – +1 Natural Armor</li>
<li><strong>Lenses of detection</strong> (3500) – These circular prisms let their wearer detect minute details, gaining a +5 competence bonus on <a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/skills/perception.html#perception">Perception</a> checks. It also aids in tracking, adding a +5 competence bonus on <a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/skills/survival.html#survival">Survival</a> checks when tracking.</li>
<li><strong>Snakeskin tunic</strong> (8000) – A snakeskin tunic is a tight, form-fitting shirt crafted from the scales of a giant snake. When worn, it grants a +1 armor bonus to AC, a +2 enhancement bonus to Dexterity, and a +2 resistance bonus on saving throws against poison.</li>
<li><strong>Bracers of armor +3</strong> (9000) – +3 armor bonus</li>
<li><strong>Verdant boots</strong> (12000) – The wearer of these boots can, on command three times per day, cause her current square to sprout a thick canopy of fruit-bearing or otherwise edible plants. These plants count as difficult terrain and grant cover to any Medium or smaller creature within the square. They also provide enough food to sustain two Medium creatures for 1 full day. While the plants can grow on surfaces that would not normally support vegetation such as a wooden floor or cave stone, they cannot sprout on surfaces explicitly hostile to vegetation. The plants are usually of a sort common to the terrain or climate of the area in which they were produced. They disappear after 24 hours or when completely harvested, whichever comes first.</li>
<li><strong>Ring of sustenance</strong> (2500) – This ring continually provides its wearer with life-sustaining nourishment. The ring also refreshes the body and mind; its wearer only needs to sleep 2 hours per day to gain the benefit of 8 hours of sleep. This allows a spellcaster that requires rest to prepare spells to do so after only 2 hours, but does not allow a spellcaster to prepare spells more than once per day. The ring must be worn for a full week before it begins to work. If it is removed, the owner must wear it for another week to reattune it to himself.</li>
<li><strong>Ring of protection +2</strong>(8000) – his ring offers continual magical protection in the form of a deflection bonus of +2 AC</li>
<li><strong>Quaterstaff</strong></li>
</ul>
<h2><strong>Skills</strong></h2>
<p>(ranks only, 3 skills maxed by headband, 7 ranks per level)</p>
<table>
<thead>
<tr>
<th>Skill</th>
<th>Rank</th>
<th>Total</th>
</tr>
</thead>
<tbody>
<tr>
<td>Knowledge (dungeoneering)</td>
<td>11</td>
<td>22</td>
</tr>
<tr>
<td>Perception</td>
<td>11</td>
<td>18</td>
</tr>
<tr>
<td>Knowledge (arcana)</td>
<td>11</td>
<td>22</td>
</tr>
<tr>
<td>Spellcraft</td>
<td>11</td>
<td>22</td>
</tr>
<tr>
<td>Bluff</td>
<td>11</td>
<td>12</td>
</tr>
<tr>
<td>Stealth</td>
<td>11</td>
<td>18</td>
</tr>
<tr>
<td>Knowledge (planes)</td>
<td>9</td>
<td>20</td>
</tr>
<tr>
<td>Disguise</td>
<td>9</td>
<td>10</td>
</tr>
<tr>
<td>Craft(Sculpting)</td>
<td>7</td>
<td>20</td>
</tr>
<tr>
<td>Linguistics</td>
<td>5</td>
<td>16</td>
</tr>
<tr>
<td>Fly</td>
<td>4</td>
<td>10</td>
</tr>
<tr>
<td>Knowledge (nature)</td>
<td>4</td>
<td>16</td>
</tr>
<tr>
<td>Knowledge (nobility)</td>
<td>1</td>
<td>13</td>
</tr>
<tr>
<td>Knowledge (engineering)</td>
<td>1</td>
<td>13</td>
</tr>
<tr>
<td>Knowledge (religion)</td>
<td>1</td>
<td>13</td>
</tr>
<tr>
<td>Knowledge (geography)</td>
<td>1</td>
<td>13</td>
</tr>
<tr>
<td>Knowledge (history)</td>
<td>1</td>
<td>13</td>
</tr>
<tr>
<td>Knowledge (local)</td>
<td>1</td>
<td>13</td>
</tr>
</tbody>
</table>
<p><strong>Situational Bonus:</strong></p>
<ul>
<li>+10 Disguise when using Hat of Disguise</li>
<li>+2 resistance bonus on saving throws against poison</li>
<li>+2 racial saving throw bonus against illusion spells and effects.</li>
</ul>
<h2>Spells</h2>
<h3>Level 1</h3>
<ul>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/alarm.html#alarm">Alarm</a></strong>: Wards an area for 2 hours/level.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/mageArmor.html#mage-armor">Mage Armor</a></strong>: Gives subject +4 armor bonus.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/unseenServant.html#unseen-servant">Unseen Servant</a></strong>: Invisible force obeys your commands.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/shield.html#shield">Shield</a></strong>: Invisible disc gives +4 to AC, blocks <em>magic missiles.</em></li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/mount.html#mount">Mount</a></strong>: Summons riding horse for 2 hours/level.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/sleep.html#sleep">Sleep</a></strong>: Puts 4 HD of creatures into magical slumber.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/colorSpray.html#color-spray">Color Spray</a></strong>: Knocks unconscious, blinds, and/or stuns weak creatures.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/disguiseSelf.html#disguise-self">Disguise Self</a></strong>: Changes your appearance.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/silentImage.html#silent-image">Silent Image</a></strong>: Creates minor illusion of your design.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/erase.html#erase">Erase</a></strong>: Mundane or magical writing vanishes.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/expeditiousRetreat.html#expeditious-retreat">Expeditious Retreat</a></strong>: Your base speed increases by 30 ft.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/featherFall.html#feather-fall">Feather Fall</a></strong>: Objects or creatures fall slowly.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/chillTouch.html#chill-touch">Chill Touch</a></strong>: One touch/level deals 1d6 damage and possibly 1 Str damage.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/hypnotism.html#hypnotism">Hypnotism</a></strong>: Fascinates 2d4 HD of creatures.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/magicMissile.html#magic-missile">Magic Missile</a></strong>: 1d4+1 damage; +1 missile per two levels above 1st (max 5).</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/grease.html#grease">Grease</a></strong>: Makes 10-ft. square or one object slippery.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/charmPerson.html#charm-person">Charm Person</a></strong>: Makes one person your friend.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/rayOfEnfeeblement.html#ray-of-enfeeblement">Ray of Enfeeblement</a></strong>: Ray causes 1d6 Str penalty + 1 per 2 levels.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/advancedPlayersGuide/spells/vanish.html#vanish">Vanish</a></strong>: As <em>invisibility</em> for 1 round/level (5 max).</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/obscuringMist.html#obscuring-mist">Obscuring Mist</a></strong>: Fog surrounds you.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/protectionFromEvil.html#protection-from-evil">Protection from Evil</a></strong>: +2 to AC and saves, plus additional protection against selected alignment.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/ultimateMagic/spells/snapdragonFireworks.html#snapdragon-fireworks">Snapdragon Fireworks</a></strong>: Create 1 dragon firework/level.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/floatingDisk.html#floating-disk">Floating Disk</a></strong>: Creates 3-ft.-diameter horizontal disk that holds 100 lbs./level.</li>
</ul>
<h3>Level 2</h3>
<ul>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/blindnessDeafness.html#blindness-deafness">Blindness/Deafness</a></strong>: Makes subject blinded or deafened.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/minorImage.html#minor-image">Minor Image</a></strong>: As <em>silent image</em>, plus some sound.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/resistEnergy.html#resist-energy">Resist Energy</a></strong>: Ignores first 10 (or more) points of damage per attack from specified energy type.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/falseLife.html#false-life">False Life</a></strong>: Gain 1d10 temporary hp + 1/level (max +10).</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/detectThoughts.html#detect-thoughts">Detect Thoughts</a></strong>: Allows &quot;listening&quot; to surface thoughts.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/ghoulTouch.html#ghoul-touch">Ghoul Touch</a></strong>: Paralyzes one subject, which exudes stench that makes those nearby sickened.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/hypnoticPattern.html#hypnotic-pattern">Hypnotic Pattern</a></strong>: Fascinates 2d4 + level HD of creatures.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/summonMonster.html#summon-monster-ii">Summon Monster II</a></strong>: Summons extraplanar creature to fight for you.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/makeWhole.html#make-whole">Make Whole</a></strong>: Repairs an object.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/foxSCunning.html#fox-s-cunning">Fox's Cunning</a></strong>: Subject gains +4 to Int for 1 min./level.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/levitate.html#levitate">Levitate</a></strong>: Subject moves up and down at your direction.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/scorchingRay.html#scorching-ray">Scorching Ray</a></strong>: Ranged touch attack deals 4d6 fire damage, + 1 ray/four levels (max 3).</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/mirrorImage.html#mirror-image">Mirror Image</a></strong>: Creates decoy duplicates of you.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/invisibility.html#invisibility">Invisibility</a></strong>: Subject is invisible for 1 min./level or until it attacks.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/spiderClimb.html#spider-climb">Spider Climb</a></strong>: Grants ability to walk on walls and ceilings.</li>
</ul>
<p> </p>
<h3>Level 3</h3>
<ul>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/explosiveRunes.html#explosive-runes">Explosive Runes</a></strong>: Deals 6d6 damage when read.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/phantomSteed.html#phantom-steed">Phantom Steed</a></strong>: Magic horse appears for 1 hour/level.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/illusoryScript.html#illusory-script">Illusory Script</a></strong>: Only select creatures can read text.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/majorImage.html#major-image">Major Image</a></strong>: As <em>silent image</em>, plus sound, smell and thermal effects.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/dispelMagic.html#dispel-magic">Dispel Magic</a></strong>: Cancels one magical spell or effect.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/ultimateMagic/spells/loathsomeVeil.html#loathsome-veil">Loathsome Veil</a></strong>: Nauseates and/or sickens weak creatures.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/protectionFromEnergy.html#protection-from-energy">Protection from Energy</a></strong>: Absorbs 12 points/level of damage from one kind of energy.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/displacement.html#displacement">Displacement</a></strong>: Attacks miss subject 50% of the time.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/vampiricTouch.html#vampiric-touch">Vampiric Touch</a></strong>: Touch deals 1d6 damage per two levels; caster gains damage as temporary hp.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/tinyHut.html#tiny-hut">Tiny Hut</a></strong>: Creates shelter for 10 creatures.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/tongues.html#tongues">Tongues</a></strong>: Speak and understand any language.</li>
</ul>
<h3>Level 4</h3>
<ul>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/dimensionDoor.html#dimension-door">Dimension Door</a></strong>: Teleports you a short distance.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/wallOfIce.html#wall-of-ice">Wall of Ice</a></strong>: <em>Ice plane</em> creates wall or <em>hemisphere</em> creates dome.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/fireTrap.html#fire-trap">Fire Trap</a></strong>: Opened object deals 1d4 damage + 1/level.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/bestowCurse.html#bestow-curse">Bestow Curse</a></strong>: –6 to an ability score; –4 on attack rolls, saves, and checks; or 50% chance of losing each action.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/summonMonster.html#summon-monster-iv">Summon Monster IV</a></strong>: Summons extraplanar creature to fight for you.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/shadowConjuration.html#shadow-conjuration">Shadow Conjuration</a></strong>: Mimics conjuration below 4th level, but only 20% real.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/secureShelter.html#secure-shelter">Secure Shelter</a></strong>: Creates sturdy cottage.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/phantasmalKiller.html#phantasmal-killer">Phantasmal Killer</a></strong>: Fearsome illusion kills subject or deals 3d6 damage.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/confusion.html#confusion">Confusion</a></strong>: Subjects behave oddly for 1 round/level.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/ultimateMagic/spells/simulacrum.html#simulacrum,-lesser">Simulacrum, Lesser</a></strong>: Creates a double of a weak creature.</li>
</ul>
<h3>Level 5</h3>
<ul>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/mirageArcana.html#mirage-arcana">Mirage Arcana</a></strong>: As <em>hallucinatory terrain,</em> plus structures.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/ultimateMagic/spells/corrosiveConsumption.html#corrosive-consumption">Corrosive Consumption</a></strong>: Acidic patch damages an opponent.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/coneOfCold.html#cone-of-cold">Cone of Cold</a></strong>: 1d6/level cold damage.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/summonMonster.html#summon-monster-v">Summon Monster V</a></strong>: Summons extraplanar creature to fight for you.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/advancedPlayersGuide/spells/phantasmalWeb.html#phantasmal-web">Phantasmal Web</a></strong>: Catches subjects in illusory web.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/ultimateMagic/spells/wallOfSound.html#wall-of-sound">Wall of Sound</a></strong>: Sonic wall deflects and damages creatures.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/shadowEvocation.html#shadow-evocation">Shadow Evocation</a></strong>: Mimics evocation below 5th level, but only 20% real.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/wallOfStone.html#wall-of-stone">Wall of Stone</a></strong>: Creates a stone wall that can be shaped.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/teleport.html#teleport">Teleport</a></strong>: Instantly transports you as far as 100 miles per level.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/overlandFlight.html#overland-flight">Overland Flight</a></strong>: You fly at a speed of 40 ft. and can hustle over long distances.</li>
</ul>
<h3>Level 6</h3>
<ul>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/chainLightning.html#chain-lightning">Chain Lightning</a></strong>: 1d6/level damage and 1 secondary bolt/level.</li>
<li><strong><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/shadowWalk.html#shadow-walk">Shadow Walk</a></strong>: Step into shadow to travel rapidly.</li>
</ul>
<p> </p>
<p> </p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2016-10-31-ghost-sound-the-ultimate-utility/</guid>
      <title>Ghost Sound – The Ultimate Utility</title>
      <link>https://blog.libove.org/posts/2016-10-31-ghost-sound-the-ultimate-utility/</link>
      <pubDate>Mon, 31 Oct 2016 15:42:25 +0000</pubDate>
      <description><![CDATA[<p><a href="http://paizo.com/pathfinderRPG/prd/spells/ghostSound.html#ghost-sound">Ghost Sound</a> is one of my favorite spells. It's a great utility spell, that can be really powerful in many situations. It can be used in combat as well as in role playing.</p>
<p>Ghost Sound is a level 0 illusion spell available to bards, sorcerer and wizards. Gnome Magic allows gnomes to use Ghost Sound once per day as a spell like ability. It last 1 round per caster level and produces as much noise as 4 humans per caster level. The produced sound can be virtually anything.</p>
<p>The usage of Ghost Sound always depends on the situation. In combat it can be used to create a distraction, may allowing your allies to land attacks of opportunity or delaying actions of your enemies. Out of combat Ghost Sound can be used to support a lie or distract guards. I gathered some examples below.</p>
<h2>In combat</h2>
<p>This was one of the first times I successfully used Ghost Sound. My group fought a bunch of goblins in a cave. We were out numbered, the melee fighters were completely surrounded. As I had no higher spells left I used Ghost Sound to do something. Creating the sound of 16 charging Knights in the tunnel leading into the cavern, I were able to convince some goblins, that our back up troops arrived. Most of them tried to flee allowing the fighters to get the upper hand in the fight.</p>
<p>To use Ghost Sound in such a way, it is important to make it convincing. Firstly the source of the sound should not be directly visible to the target. Not even goblins will believe you, if you create the sound of an charging army on an empty plane. Secondly the produced sound should fit into the surrounding. If you are in a cave use the sound of giant insects, goblins and kobolds instead of bisons. The Bestiaries provide <a href="http://paizo.com/pathfinderRPG/prd/monsters/monstersByTerrain.html">lists of monsters for every terrain type</a>. At last choose sounds creating fear in your enemies. The descriptions of monsters often state their enemies or fears. You might can find something by knowledge rolls.</p>
<p>As a gnome you can use the metamagic feat <a href="http://www.d20pfsrd.com/feats/metamagic-feats/threatening-illusion-metamagic">Threatening Illusion</a> to flank a foe. The targeted field is considered as threatening enemies. The feat raises the spell level by one but a threatening Ghost Sound is still a good choice for level one spells. The effect probably will only last one round, but your rogue will thank you.</p>
<h2>In role playing</h2>
<p>The real strength of Ghost Sound comes into play once combat is over. The spell can be used in almost any situation.</p>
<p>Causing distraction is probably the most common use of Ghost Sound. It begins at the towns gate by creating the sound of a brawl to distract the guards to avoid paying 2 silver coins per feet. When you reach the market distract a merchant with the sound of fighting chickens in his cages, to allow your rogue to acquire a splendid lunch. Whenever a NPC has to be distracted, Ghost Sound is a good first choice.</p>
<p>Ghost Sound can be used to support your statements or to alter the attitude of an NPC. If you want to convince a NPC that the group on the next table is plotting to kill him, we will be more likely to believe you if he hears constant mumbling. The guards might help you if the bandits scream about killing guards and taking their wives. General advise: let the target hear what he wants to hear in order to support your own needs.</p>
<h2>Conclusion</h2>
<p>As Ghost Sound is a level 0 spell it is tempting to use it all the time. However keep in mind that Ghost Sound is only as effective as your game masters allows it to be. At some point the GM will snap, if you repeat the same trick over and over again. My advise: only use Ghost Sound occasionally and describe it precisely. Also explain what effect you are looking for. You won't always get the wanted effect, but putting more effort in the descriptions makes it more likely.</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2016-10-30-pathfinder-character-black-courtesan/</guid>
      <title>Pathfinder Character: Black Courtesan</title>
      <link>https://blog.libove.org/posts/2016-10-30-pathfinder-character-black-courtesan/</link>
      <pubDate>Sun, 30 Oct 2016 21:24:42 +0000</pubDate>
      <description><![CDATA[<p>I've played this character in the previous campaign(s) of our group and it got retired. The idea was to create a strong social character that also fulfills the role of a classical rogue (disabling traps and opening doors). She still has some use in combat, but is very squishy and should stay hidden or behind the tank at any time.</p>
<p>Vashta Nerada (<a href="http://tardis.wikia.com/wiki/Vashta_Nerada">name origin</a>) is a female half-elven rogue. She used to work as a courtesan but found people tend to pay more for the possessions of her clients than the clients for her services. Being a trained courtesan she has no problem to seduce victims and infiltrate mansions and strongholds. Her social skills and profession allow her to penetrate strong security measures and obtain objects of desire while the owner is fast asleep.</p>
<p><strong>Name:</strong> Vashta Nerada</p>
<p><strong>Race:</strong> Half-Elf</p>
<p><strong>Class:</strong> Rogue/10</p>
<h2>Ability Scores {<span class="hashtag"><a class="p-category" href="/tags/ability-scores/">#ability-scores</a></span>}</h2>
<p><strong>Raw:</strong> <strong>STR:</strong> 10 <strong>DEX:</strong> 18 <strong>CON:</strong> 10 <strong>INT:</strong> 14 <strong>WIS:</strong> 10 <strong>CHA:</strong> 18</p>
<p><strong>Equipped:</strong> <strong>STR:</strong> 10 <strong>DEX:</strong> 22 <strong>CON:</strong> 10 <strong>INT:</strong> 14 <strong>WIS:</strong> 10 <strong>CHA:</strong> 20</p>
<p>(<strong>Point Buy 25:</strong> <strong>STR:</strong> 10 <strong>DEX:</strong> 16 <strong>CON:</strong> 10 <strong>INT:</strong> 14 <strong>WIS:</strong> 10 <strong>CHA:</strong> 16, <strong>+2 DEX</strong> Race Bonus, <strong>+2 CHA</strong> from level 4+8)</p>
<h2><strong>Combat</strong></h2>
<p><strong>Hit Points:</strong> 10D8</p>
<p><strong>Initiative:</strong> 8</p>
<p><strong>Reflex:</strong> 13 <strong>Fortitude:</strong> 3 <strong>Willpower:</strong> 3</p>
<p><strong>Armor Class:</strong> 22 <strong>Touch:</strong> 16 <strong>Flat-footed:</strong> 16</p>
<p><strong>CMB:</strong> 7 <strong>CMD:</strong> 23</p>
<p><strong>Shortbow:</strong> Attacks: +16/+11 Damage: 1d6+2 Crit: 20/x3</p>
<p><strong>Shortbow(30 feet):</strong> Attacks: +17/+12 Damage: 1d6+2 (+5d6 +5 bleed on Sneak Attack)  Crit: 20/x3</p>
<p><strong>Dagger:</strong> Attacks: +14/+9 Damage: 1d4 (+5d6 +5 bleed on Sneak Attack)  Crit: 19-20/x2</p>
<h2><strong>Feats</strong></h2>
<p>(5 regular + 1 skill focus from race)</p>
<ul>
<li><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/feats.html#point-blank-shot">Point-Blank Shot</a> – +1 attack and damage on targets within 30 feet</li>
<li><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/feats.html#precise-shot">Precise Shot</a> – No penalty for shooting into melee</li>
<li><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/feats.html#weapon-finesse">Weapon Finesse</a> – Use DEX instead of STR on attack rolls with light weapons</li>
<li><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/feats.html#weapon-focus">Weapon Focus</a>(Short Bow) – +1 bonus on attack rolls with one weapon</li>
<li><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/feats.html#skill-focus">Skill Focus</a>(Perception) – +3 bonus on one skill (+6 at 10 ranks)</li>
<li><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/feats.html#skill-focus">Skill Focus</a>(Stealth) – +3 bonus on one skill (+6 at 10 ranks)</li>
</ul>
<h2><strong>Rogue Talents</strong></h2>
<p>(4 Normal, 1 Advanced )</p>
<ul>
<li><strong>Bleeding Attack:</strong> A rogue with this ability can cause living opponents to bleed by hitting them with a sneak attack.</li>
<li><strong>Fast Stealth:</strong> This ability allows a rogue to move at full speed using the Stealth skill without penalty.</li>
<li><strong>Trap Spotter:</strong> Whenever a rogue with this talent comes within 10 feet of a trap, she receives an immediate Perception skill check to notice the trap.</li>
<li><strong>Sniper's Eye:</strong> A rogue with this talent can apply her sneak attack damage on ranged attacks targeting foes within 30 feet that benefit from concealment.</li>
<li><strong>Stealthy Sniper:</strong> When a rogue with this talent uses the Stealth skill to snipe, she only suffers a –10 penalty on the Stealth check, instead of –20.</li>
</ul>
<h2><strong>Traits</strong></h2>
<ul>
<li><strong>Charming</strong>: Blessed with good looks, you've come to depend on the fact that others find you attractive. You gain a +1 trait bonus when you use Bluff or Diplomacy on a character that is (or could be) sexually attracted to you, and a +1 trait bonus to the save DC of any language-dependent spell you cast on such characters or creatures.</li>
<li><strong>Elven Reflexes</strong>: One of your parents was a member of a wild elven tribe, and you've inherited a portion of your elven parent's quick reflexes. You gain a +2 trait bonus on Initiative checks.</li>
</ul>
<h2><strong>Equipment</strong></h2>
<p>(62000 GP at level 10)</p>
<ul>
<li><strong>Darkwood Shortbow +2</strong>(8350)</li>
<li><strong>Dagger +1</strong>(2302)</li>
<li><strong>Mithral Chain Shirt +2</strong>(5300)</li>
<li><strong>Corset of the vishkanya</strong> (3000): As a free action, the wearer can compress herself to fit through tight spaces as though affected by a <em>squeeze</em> spell (<em>Advanced Race Guide</em>) for up to 10 rounds per day. While using the corset's magic, she gains a +5 bonus on <a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/skills/escapeArtist.html#escape-artist">Escape Artist</a> checks.</li>
<li><strong>Cloak of Elvenkind <strong>(2500)</strong>:</strong> +5 Stealth</li>
<li><strong>Eyes of the Eagle</strong> (2500): +5 Perception</li>
<li><strong>Vest of Escape</strong> (5200):  Hidden inside these are magic lockpicks that provide a +4 competence bonus on Disable Device checks. The vest also grants its wearer a +6 competence bonus on Escape Artist checks.</li>
<li><strong>Belt of incredible dexterity +4</strong> (16000): +4 Dexterity</li>
<li><strong>Boots of the Winterland</strong> (2500): The wearer of these boots is able to travel across snow at her normal speed, leaving no tracks. (And more)</li>
<li><strong>Hat of Disguise</strong> (1800): This apparently normal hat allows its wearer to alter her appearance as with a <a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/disguiseSelf.html#disguise-self">disguise self</a> spell.</li>
<li><strong>Headband of alluring charisma +2</strong> (4000): +2 Charisma</li>
<li><strong>Mind sentinel medallion</strong> (3500): The medallion grants a continuous +2 resistance bonus on saves versus mind-affecting spells, spell-like abilities, and supernatural abilities.</li>
<li><strong>Sleeves of many garments</strong> (200): The wearer of these sleeves can, when she slips them on, choose to transform her current garments into any other nonmagical set of clothing.</li>
<li><strong>Apprentice's cheating gloves</strong> (2200): The wearer of these gloves can employ <em><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/mageHand.html#mage-hand">mage hand</a></em> and <em><a href="http://paizo.com/pathfinderRPG/prd/coreRulebook/spells/prestidigitation.html#prestidigitation">prestidigitation</a></em> at will.</li>
<li><strong>2648 GP</strong> for other equipment and toys.</li>
</ul>
<h2><strong>Skills</strong></h2>
<p>(ranks only, 11 ranks per level)</p>
<table>
<thead>
<tr>
<th>Skill</th>
<th>Rank</th>
<th>Total</th>
</tr>
</thead>
<tbody>
<tr>
<td>Acrobatics</td>
<td>10</td>
<td>19</td>
</tr>
<tr>
<td>Stealth</td>
<td>10</td>
<td>30</td>
</tr>
<tr>
<td>Diplomacy</td>
<td>10</td>
<td>18</td>
</tr>
<tr>
<td>Disable Device</td>
<td>10</td>
<td>22</td>
</tr>
<tr>
<td>Disguise</td>
<td>10</td>
<td>18</td>
</tr>
<tr>
<td>Sleight of Hand</td>
<td>10</td>
<td>19</td>
</tr>
<tr>
<td>Sense Motive</td>
<td>10</td>
<td>13</td>
</tr>
<tr>
<td>Perception</td>
<td>10</td>
<td>24</td>
</tr>
<tr>
<td>Bluff</td>
<td>10</td>
<td>18</td>
</tr>
<tr>
<td>Knowledge (local)</td>
<td>5</td>
<td>10</td>
</tr>
<tr>
<td>Linguistics</td>
<td>3</td>
<td>8</td>
</tr>
<tr>
<td>Climb</td>
<td>4</td>
<td>7</td>
</tr>
<tr>
<td>Intimidate</td>
<td>3</td>
<td>11</td>
</tr>
<tr>
<td>Escape Artist</td>
<td>2</td>
<td>11</td>
</tr>
<tr>
<td>Swim</td>
<td>1</td>
<td>4</td>
</tr>
<tr>
<td>Perform (Dance)</td>
<td>1</td>
<td>9</td>
</tr>
<tr>
<td>Knowledge (dungeoneering)</td>
<td>1</td>
<td>6</td>
</tr>
</tbody>
</table>
<p><strong>Situational Bonus:</strong></p>
<ul>
<li>+10 Disguise when using Hat of Disguise</li>
<li>+5 to find and disable traps (Trapfinding)</li>
<li>+3 Reflex/AC vs traps (Trap Sense)</li>
<li>+2 Resistance bonus versus mind-affecting spells, spell-like abilities, and supernatural abilities</li>
<li>+1 Bluff/Diplomacy versus characters that are (or could be) sexually attracted to you (Charming)</li>
</ul>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2016-09-24-aligning-player-movement-with-camera/</guid>
      <title>Aligning Player Movement with Camera</title>
      <link>https://blog.libove.org/posts/2016-09-24-aligning-player-movement-with-camera/</link>
      <pubDate>Sat, 24 Sep 2016 21:48:17 +0000</pubDate>
      <description><![CDATA[<p>In a game with a static camera (looking down at the level) it is important to align the character movement with the camera. Failing to do so creates a distorted input feeling as the input and the actions of the player character don't correspond to each other. To achieve a correct movement, the directions have to projected from the screen onto the scene.</p>
<p><img src="media/incorrect.gif" alt="Wrong movement along axis of the coordinate system."></p>
<p>Naive implementations of player movement use the axis of the coordinate system as directions. Left and Right correspond to the [1,0,0] direction and Up and Down to the[0,0,1] direction. This implementation is correct and usable as long as the camera is correctly aligned to this axis. If the camera is not aligned the movement directions no longer correspond to the perceived directions of the player (see image above). To correct this we have to extract the directions from the camera.</p>
<p><img src="media/correct.gif" alt="Corrected movement along projected directions."></p>
<p>To compute our new directions we unproject three points from camera space to world space. Most framework/engine have built in functions to do this ( <a href="http://docs.godotengine.org/en/latest/classes/class_camera.html?highlight=camera#class-camera-project-position">godot</a>, <a href="https://www.opengl.org/sdk/docs/man2/xhtml/gluUnProject.xml">opengl</a>, <a href="https://docs.unity3d.com/ScriptReference/Camera.ViewportPointToRay.html">unity</a> ). We have to preform 3 unprojections for the points: (0,0), (1,0) and (0,1). By substracting the result of latter two from the former we obtain two 3D-Vectors that represent our new directions. To receive planar movement direction set the height value (mostly <em>y</em>) to zero and normalize the vectors.</p>
<h3>Godot:</h3>
<pre><code>var p0 = cam.project_position(Vector2(0,0))
var pH = cam.project_position(Vector2(10,0))
var pV = cam.project_position(Vector2(0,10))

camH = pH - p0
camV = pV - p0
camH.y = 0
camV.y = 0
camH = camH.normalized()
camV = camV.normalized()
</code></pre>
<p>camH is the horizontal movement direction and camV the vertical. Instead of using the axis of the coordinate system ([1,0,0] and [0,0,1]) as movement directions, use camH for left/right movement and camV for up/down movement.</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2016-09-24-released-3-small-asset-packs/</guid>
      <title>Released 3 Small Asset Packs</title>
      <link>https://blog.libove.org/posts/2016-09-24-released-3-small-asset-packs/</link>
      <pubDate>Sat, 24 Sep 2016 16:07:39 +0000</pubDate>
      <description><![CDATA[<p><a href="https://h4kor.itch.io/low-poly-village-buildings">Low Poly Village Buildings</a></p>
<p><a href="https://h4kor.itch.io/low-poly-desert-arena">Low Poly Desert Arena</a></p>
<p><a href="https://h4kor.itch.io/walkaimshoot-assets">Pixel Art Terrain</a></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2016-09-19-pasta-arugula-salad/</guid>
      <title>Pasta Arugula Salad</title>
      <link>https://blog.libove.org/posts/2016-09-19-pasta-arugula-salad/</link>
      <pubDate>Mon, 19 Sep 2016 19:15:19 +0000</pubDate>
      <description><![CDATA[<p><a href="/media/header.jpg"><img src="/thumbnail/header.jpg" alt=""></a></p>
<p>This is my favorite salad. It's suitable for most occasions and can serve as a main or side dish. It takes around 20 minutes to prepare.</p>
<p><a href="/media/ingredients.jpg"><img src="/thumbnail/ingredients.jpg" alt=""></a></p>
<p>The ingredients (in a copyable fashion):</p>
<ul>
<li>500 g Pasta</li>
<li>250 g Mini Tomatoes</li>
<li>250 g Mozzarella</li>
<li>125 g Arugula</li>
<li>100 g Green Pesto</li>
<li>2 Tablespoons Honey</li>
<li>2 Tablespoons Mustard</li>
<li>1 Handful Grinded Parmesan Cheese</li>
</ul>
<p><a href="/media/cut_and_cook.jpg"><img src="/thumbnail/cut_and_cook.jpg" alt=""></a></p>
<p> </p>
<p>Cook the pasta as described on the package (al dente). While the pasta is cooking you can cut the mozzarella and the tomatoes. I divide the tomatoes once or twice, depending on their size. The mozzarella is cut into pieces roughly the same size as the tomato pieces.</p>
<p><a href="/media/mixing_rvvDvxV.jpg"><img src="/thumbnail/mixing_rvvDvxV.jpg" alt=""></a></p>
<p>Add everything to the bowl, except the arugula! It takes a long time mix everything up if you add the arugula directly. Mix the ingredients until everything is coated than add the arugula and mix it again.</p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2016-09-13-how-to-create-a-city-map-gif/</guid>
      <title>How to create a city map (GIF)</title>
      <link>https://blog.libove.org/posts/2016-09-13-how-to-create-a-city-map-gif/</link>
      <pubDate>Tue, 13 Sep 2016 16:40:41 +0000</pubDate>
      <description><![CDATA[<p><img src="media/draw_a_city_slow.gif" alt="Simple steps to create a map of a city."></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2016-09-13-giving-pathfinder-professions-more-meaning/</guid>
      <title>Giving Pathfinder Professions More Meaning!</title>
      <link>https://blog.libove.org/posts/2016-09-13-giving-pathfinder-professions-more-meaning/</link>
      <pubDate>Tue, 13 Sep 2016 16:29:18 +0000</pubDate>
      <description><![CDATA[<p>I wanted to add additional mechanics to professions in Pathfinder to give them more meaning. At the moment the only usage of a profession is to earn some money. Occasionally some professions can be used in other ways; for example fixing a cart. These additional mechanics should give players more incentive to pick a profession, instead of spending their points on perception.</p>
<h1>Cook</h1>
<h3>Preparing a Lavish Meal:</h3>
<p>To prepare a Lavish Meal you need one pound of fresh ingredients per small or normal creature to feed. Additionally you need spices worth at least 1 gp. per person. It takes one hour to prepare the meal. If you want to feed more than 10 people it takes an additional hour per 10 people. You need a fire, or any other sufficient heat source, a pan and a pot.</p>
<p>Everyone eating a full Lavish Meal is healed. The amount of HP recovered depends on the Profession(Cooking) throw of the cook. A normal Lavish Meal heals 2 HP. You can only benefit from a Lavish Meal once per day.</p>
<p>The DC Profession(Cooking) to create a Lavish Meal is 20. For every 5 by which your check result exceeds the DC, the meal heals additional 2 hit points.</p>
<h1>Woodcutter</h1>
<h3>Find Weak Spot:</h3>
<p>You can analyse wooden objects, e.g. wooden doors, to find their weak spots to break them more easily. If you know the weak spot of an object the Strength DC to break it is lowered by one point. Additionally the objects hardness is lowered by one point.</p>
<p>It takes 1 minute to find a weak spot. You can use the bonus as long as you can see and access the found weak spot. Once you found one, you can describe it to other persons to allow them the same bonus. You have to stand next to the person, when she is breaking or attacking the object.</p>
<p>The DC Profession(Woodcutter) to find a weak spot is 20. For every 5 by which your check result exceeds the DC, the Strength DC to break the object and the hardness are lowered by an additional point.</p>
<h1>Brewer</h1>
<h3>Splendid Drink</h3>
<p>To prepare a Splendid Drink you need herbs worth at least 2 gp. per small or normal person. You can create at most 10 drinks with one Profession(Brewer) throw. It takes 30 minutes to prepare the drink. The drink has to cook for 4 hours before it is usable. You do not have to tend the drink while it is cooked. Splendid Drinks can be filled into flasks, waterskins or similar containers. A Splendid Drink has to be used 1 week after its creation or its effect is lost.</p>
<p>The DC Profession(Brewer) for brewing a Splendid Drink is 20. A Splendid Drink grants a +1 bonus to any skill check for 10 minutes. For every 5 by which your check result exceeds the DC Profession(Brewer), the drink grants an additional +1 bonus on skill checks. Tasks that take longer than 10 minutes, e.g. crafting, do not benefit from Splendid Drink. A Creature can only benefit from a Splendid Drink once per day.</p>
<h1>Scribe</h1>
<h3>Prepare Letter of Recommendation</h3>
<p>It takes 1 hour to create a Letter of Recommendation. You need parchment and ink worth at least 1 gp. and a quiet environment with a suitable surface to write on.</p>
<p>A Letter of Recommendation gives you a +2 bonus to one diplomacy throw against a merchant or an official instance, e.g. a mayor or town guard. The person the Letter of Recommendation is used against will keep it, regardless the result of the diplomacy throw. You can only use one Letter of Recommendation per diplomacy throw.</p>
<p>The DC Profession(Scribe) to create a Letter of Recommendation is 20. For every 5 by which your check result exceeds the DC, the diplomacy bonus granted by the Letter is increased by two points.</p>
<h1>Driver</h1>
<h3>Spare Horses:</h3>
<p>While driving a curt you can use Spare Horses to travel longer. You need to drive the cart or wagon the full 8 hours to uses be able to use Spare Horses. You can also use the skill when the cart is pulled by other animals than horses.</p>
<p>Spare Horses allows you to travel 1 hour longer before the horses get exhausted. The DC Profession(Driver) of Spare Horses is 20. For every 5 by which your check result exceeds the DC, you can travel an extra hour before your horses get exhausted.</p>
<h1>Librarian</h1>
<h3>Support Research:</h3>
<p>You can support an ally, including yourself, in his research. You need access to library or a book collection with at least 100 books. You have to support your ally for at least 1 hour. A person can only be supported once per knowledge check.</p>
<p>The DC Profession(Librarian) of Support Research is 20. A person you support in his research gains a +2 bonus on his knowledge check. For every 5 by which your check result exceeds the DC Profession(Librarian), the target gains an extra +2 bonus to its knowledge check.</p>
<h1>Gambler</h1>
<h3>Test your Luck:</h3>
<p>Your are used to risk everything for the chance of winning some coins. Even outside the tavern you can test your luck. By testing you luck you gain a luck points applicable to skill, attack and saving throws.</p>
<p>Test you luck is a move action. If success you gain luck points. You have to use the luck points in the next round or they are discarded. Luck points are applicable to skill, attack and saving throws. You have to apply the luck points before throwing the dice. You can split up you luck points, for example using 1 point for an attack roll and 1 point for a skill check.</p>
<p>The DC Profession (Gambler) of Test your luck is 20. If you succeed you gain one luck point. For every 5 by which your check result exceeds the DC Profession (Gambler), you gain an additional luck point. Once your fail the skill check profession (Gambler) you can't use Test your Luck for the rest of the day. Additionally your next skill, attack or saving throws is performed with a malus equal to the luck points you gained that day. You can't do a useless check to dismiss the malus.</p>
<p> </p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2016-09-13-how-to-prepare-a-campaign-without-rail-roads/</guid>
      <title>How to prepare a Campaign without rail-roads</title>
      <link>https://blog.libove.org/posts/2016-09-13-how-to-prepare-a-campaign-without-rail-roads/</link>
      <pubDate>Tue, 13 Sep 2016 16:21:45 +0000</pubDate>
      <description><![CDATA[<p><em>I posted this on <a href="https://www.reddit.com/r/gamemasters/comments/2ly050/how_to_prepare_a_campaign_without_railroads_xpost/">reddit</a> a while back, just adding it here for completion.</em></p>
<p>When I plan a campaign, I separate the planning phase in two parts:</p>
<ol>
<li>Planning the main story arc</li>
<li>Preparing for a session</li>
</ol>
<h3>Planning the main story arc</h3>
<p>First figure out in which world you play want to play. Who are the big players? Kings, Gods, established heroes, etc. .</p>
<p>Create a small list of attributes for every important NPC. The list should contain their motives, morales, personality. The lists will help you to stay consistent and to act upon the PCs actions quickly.</p>
<p>As you won't be able to tell what will happen in the course of several sessions only make a rough plan of events that will happen.</p>
<p>Set up an initial list of events that will occur if the party of heroes would not exist. <em>Example: the evil guy tries to summon an evil monster in roughly 10 days.</em></p>
<p>This timetable will give you a rough framework for your campaign, without rail-roading the story. After each session you should revisit the timetable and figure out how the actions of the heroes have changed the course of history. _Example: If the PCs killed a commander of the evil forces, the raid on a village in 2 weeks can't take place._This fixed timetable also means that the players can miss certain events if they are to slow or ignore a story plot. <em>E. g. If the party needs four weeks to stop an event something else will happen without a chance for the PCs to intervene.</em> With the timetable and rough descriptions of the event you got an overall story arc that is not written in stone and can be changed by the players.</p>
<h3>Preparing for a session</h3>
<p>When planning a session take a look at the timetable to see what is up next. Think through what can happen in the next bit of the story and possible actions of the PCs. You will never find all possible decisions, so don't even bother writing it down ;).</p>
<p>When you got a rough overview for the session, you can start to prepare.</p>
<p>Create groups of enemies fitting into the scene. Create local maps of villages, dungeons and other sites you want to use. Figure out how the enemies will behave.</p>
<p><em>Example: 2 groups of goblin guard the mage tower. On a small balcony hovers an all-seeing-eye warning the mages if it sees anything unusual. The door to the mages bedroom is trapped with Trap X. etc. .</em></p>
<p>If you have difficulties improvising, spend more time describing details of the sites the heroes visit. Have a lot of stuff ready you can use if needed.</p>
<p>At the end you should have a detailed description of the places the party will visit. From that point on the players decide. Listen to their actions and look up what could happen.</p>
<p>_</p>
<p>Example: They sneak through the fields -&gt; give the goblins and the eye a perception throw and act upon it.</p>
<p>_</p>
<p>If you notice that it will get to boring, use the assets you created to add more into your world. Maybe 2 goblins decided to plunder the kitchen, and they notice the group.</p>
<p>Hopefully I gave you a rough idea how I plan my campaigns.</p>
<p><strong>TL:DR</strong></p>
<ol>
<li>Have a rough story arc for the campaign.</li>
<li>Create detailed descriptions of sites and enemies for every session and only act upon the actions of the party</li>
</ol>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2016-09-13-create-tileable-textures-with-gimp/</guid>
      <title>Create tileable textures with GIMP</title>
      <link>https://blog.libove.org/posts/2016-09-13-create-tileable-textures-with-gimp/</link>
      <pubDate>Tue, 13 Sep 2016 16:19:09 +0000</pubDate>
      <description><![CDATA[<h3>The input material</h3>
<p>I took photos of the grass in my backyard to create a grass texture. The camera was held at shoulder height pointing down. I cropped the image into a 1:1 image. Try to keep the image as big as possible. Do not scale it into your desired dimension yet!</p>
<p><a href="/media/input_image-300x300.jpg"><img src="/thumbnail/input_image-300x300.jpg" alt="input_image"></a></p>
<h3>Make it tileable!</h3>
<p>There are two methods to make the image tileable/seamless.</p>
<p>The easiest way to create a tileable texture is to use the &quot;Make Seamless&quot; tool (Filters-&gt;Map-&gt;Make Seamless).</p>
<p>If you aren't satisfied by the results of the Make Seamless filter you can also do it by hand.</p>
<p>Start by applying an x/2, y/2 offset by using the Layer-&gt;Transform-&gt;Offset tool.</p>
<p>This will offset the image in such a way that the opposite border match each other perfectly, but it only shifts the problem. You should notice that there are two lines, one horizontal, one vertical, were the image doesn't fit together. You can remove the visible borders in various ways, e.g. using the clone and blur tool.</p>
<p><a href="/media/seamless_grass-300x300.jpg"><img src="/thumbnail/seamless_grass-300x300.jpg" alt="seamless_grass"></a></p>
<h3>Removing the midrange frequencies</h3>
<p>At this point we cloud use the texture, but we will go one step further. The image still contains midrange frequencies, that we want to remove. The midrange frequencies will produce ugly tiling effects if the texture is repeated often.</p>
<p>Start by duplicating the seamless texture and apply a tileable blur (Filters-&gt;Blur-&gt;Tileable Blur) onto the duplicated version. Choose a high radius, roughly 10% of the images size. This will create a layer containing the mid and low frequencies.</p>
<p>To preserve the low frequencies create a new texture below the two existing. Use the Color Picker with a big radius to select an average color of the texture and fill the new texture with this color.</p>
<p>We now have one layer containing the mid and low frequencies, one containing the low frequencies and one containing all frequencies.</p>
<p>To create a layer containing the high frequencies set the mode of the blurred layer to &quot;Grain extract&quot; and move it above the original layer. By Merging these two layers you gain a new layer containing only the high frequencies. After merging set the mode of the resulting layer to &quot;Grain merge&quot; and merge the two remaining layers. This creates our final image containing the high and low frequencies but no mid frequencies.</p>
<p><a href="/media/finished_grass_texture-300x300.jpg"><img src="/thumbnail/finished_grass_texture-300x300.jpg" alt="finished_grass_texture"></a></p>
<p><em>Note: I scaled the images to save bandwidth</em></p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2016-09-13-pathfinder-loot-generators/</guid>
      <title>Pathfinder Loot Generators</title>
      <link>https://blog.libove.org/posts/2016-09-13-pathfinder-loot-generators/</link>
      <pubDate>Tue, 13 Sep 2016 16:08:10 +0000</pubDate>
      <description><![CDATA[<p>These are all tables of wondrous items in the <a href="http://paizo.com/products/btpy8tmc?Pathfinder-Roleplaying-Game-Ultimate-Equipment">Ultimate Equipment</a> book.</p>
<p>The content falls under the <a href="http://paizo.com/pathfinderRPG/prd/openGameLicense.html">Open Game License (OGL)</a>.</p>
<h2><span style="text-decoration: underline;">Minor Items</span></h2>
<ul>
<li><a href="http://tools.libove.org/generators/r/187/">Lesser Minor Belt Item</a></li>
<li><a href="http://tools.libove.org/generators/r/188/">Greater Minor Belt Item</a></li>
<li><a href="http://tools.libove.org/generators/r/201/">Lesser Minor Body Item</a></li>
<li><a href="http://tools.libove.org/generators/r/202/">Greater Minor Body Item</a></li>
<li><a href="http://tools.libove.org/generators/r/207/">Lesser Minor Chest Item</a></li>
<li><a href="http://tools.libove.org/generators/r/208/">Greater Minor Chest Item</a></li>
<li><a href="http://tools.libove.org/generators/r/213/">Lesser Minor Eye Item</a></li>
<li><a href="http://tools.libove.org/generators/r/214/">Greater Minor Eye Item</a></li>
<li><a href="http://tools.libove.org/generators/r/219/">Lesser Minor Feet Item</a></li>
<li><a href="http://tools.libove.org/generators/r/220/">Greater Minor Feet Item</a></li>
<li><a href="http://tools.libove.org/generators/r/225/">Lesser Minor Hand Item</a></li>
<li><a href="http://tools.libove.org/generators/r/226/">Greater Minor Hand Item</a></li>
<li><a href="http://tools.libove.org/generators/r/231/">Lesser Minor Head Item</a></li>
<li><a href="http://tools.libove.org/generators/r/232/">Greater Minor Head Item</a></li>
<li><a href="http://tools.libove.org/generators/r/237/">Lesser Minor Headband Item</a></li>
<li><a href="http://tools.libove.org/generators/r/238/">Greater Minor Headband Item</a></li>
<li><a href="http://tools.libove.org/generators/r/243/">Lesser Minor Neck Item</a></li>
<li><a href="http://tools.libove.org/generators/r/244/">Greater Minor Neck Item</a></li>
<li><a href="http://tools.libove.org/generators/r/249/">Lesser Minor Shoulders Item</a></li>
<li><a href="http://tools.libove.org/generators/r/250/">Greater Minor Shoulders Item</a></li>
<li><a href="http://tools.libove.org/generators/r/255/">Lesser Minor Wrists Item</a></li>
<li><a href="http://tools.libove.org/generators/r/256/">Greater Minor Wrists Item</a></li>
<li><a href="http://tools.libove.org/generators/r/193/">Least Minor Slotless Item</a></li>
<li><a href="http://tools.libove.org/generators/r/194/">Lesser Minor Slotless Item</a></li>
<li><a href="http://tools.libove.org/generators/r/195/">Greater Minor Slotless Item</a></li>
</ul>
<h3>Random Item Type</h3>
<ul>
<li><a href="http://tools.libove.org/generators/r/262/">Lesser Minor Wondrous Item</a></li>
<li><a href="http://tools.libove.org/generators/r/265/">Greater Minor Wondrous Item</a></li>
</ul>
<h2><span style="text-decoration: underline;">Medium Items</span></h2>
<ul>
<li><a href="http://tools.libove.org/generators/r/189/">Lesser Medium Belt Item</a></li>
<li><a href="http://tools.libove.org/generators/r/190/">Greater Medium Belt Item</a></li>
<li><a href="http://tools.libove.org/generators/r/203/">Lesser Medium Body Item</a></li>
<li><a href="http://tools.libove.org/generators/r/204/">Greater Medium Body Item</a></li>
<li><a href="http://tools.libove.org/generators/r/209/">Lesser Medium Chest Item</a></li>
<li><a href="http://tools.libove.org/generators/r/210/">Greater Medium Chest Item</a></li>
<li><a href="http://tools.libove.org/generators/r/215/">Lesser Medium Eye Item</a></li>
<li><a href="http://tools.libove.org/generators/r/216/">Greater Medium Eye Item</a></li>
<li><a href="http://tools.libove.org/generators/r/221/">Lesser Medium Feet Item</a></li>
<li><a href="http://tools.libove.org/generators/r/222/">Greater Medium Feet Item</a></li>
<li><a href="http://tools.libove.org/generators/r/227/">Lesser Medium Hand Item</a></li>
<li><a href="http://tools.libove.org/generators/r/228/">Greater Medium Hand Item</a></li>
<li><a href="http://tools.libove.org/generators/r/233/">Lesser Medium Head Item</a></li>
<li><a href="http://tools.libove.org/generators/r/234/">Greater Medium Head Item</a></li>
<li><a href="http://tools.libove.org/generators/r/239/">Lesser Medium Headband Item</a></li>
<li><a href="http://tools.libove.org/generators/r/240/">Greater Medium Headband Item</a></li>
<li><a href="http://tools.libove.org/generators/r/245/">Lesser Medium Neck Item</a></li>
<li><a href="http://tools.libove.org/generators/r/246/">Greater Medium Neck Item</a></li>
<li><a href="http://tools.libove.org/generators/r/251/">Lesser Medium Shoulders Item</a></li>
<li><a href="http://tools.libove.org/generators/r/252/">Greater Medium Shoulders Item</a></li>
<li><a href="http://tools.libove.org/generators/r/257/">Lesser Medium Wrists Item</a></li>
<li><a href="http://tools.libove.org/generators/r/258/">Greater Medium Wrists Item</a></li>
<li><a href="http://tools.libove.org/generators/r/197/">Lesser Medium Slotless Item</a></li>
<li><a href="http://tools.libove.org/generators/r/198/">Greater Medium Slotless Item</a></li>
</ul>
<h3>Random Item Type</h3>
<ul>
<li><a href="http://tools.libove.org/generators/r/266/">Lesser Medium Wondrous Item</a></li>
<li><a href="http://tools.libove.org/generators/r/267/">Greater Medium Wondrous Item</a></li>
</ul>
<h2><span style="text-decoration: underline;">Major Items</span></h2>
<ul>
<li><a href="http://tools.libove.org/generators/r/191/">Lesser Major Belt Item</a></li>
<li><a href="http://tools.libove.org/generators/r/192/">Greater Major Belt Item</a></li>
<li><a href="http://tools.libove.org/generators/r/205/">Lesser Major Body Item</a></li>
<li><a href="http://tools.libove.org/generators/r/206/">Greater Major Body Item</a></li>
<li><a href="http://tools.libove.org/generators/r/211/">Lesser Major Chest Item</a></li>
<li><a href="http://tools.libove.org/generators/r/212/">Greater Major Chest Item</a></li>
<li><a href="http://tools.libove.org/generators/r/217/">Lesser Major Eye Item</a></li>
<li><a href="http://tools.libove.org/generators/r/218/">Greater Major Eye Item</a></li>
<li><a href="http://tools.libove.org/generators/r/223/">Lesser Major Feet Item</a></li>
<li><a href="http://tools.libove.org/generators/r/224/">Greater Major Feet Item</a></li>
<li><a href="http://tools.libove.org/generators/r/229/">Lesser Major Hand Item</a></li>
<li><a href="http://tools.libove.org/generators/r/230/">Greater Major Hand Item</a></li>
<li><a href="http://tools.libove.org/generators/r/235/">Lesser Major Head Item</a></li>
<li><a href="http://tools.libove.org/generators/r/236/">Greater Major Head Item</a></li>
<li><a href="http://tools.libove.org/generators/r/241/">Lesser Major Headband Item</a></li>
<li><a href="http://tools.libove.org/generators/r/242/">Greater Major Headband Item</a></li>
<li><a href="http://tools.libove.org/generators/r/247/">Lesser Major Neck Item</a></li>
<li><a href="http://tools.libove.org/generators/r/248/">Greater Major Neck Item</a></li>
<li><a href="http://tools.libove.org/generators/r/253/">Lesser Major Shoulders Item</a></li>
<li><a href="http://tools.libove.org/generators/r/254/">Greater Major Shoulders Item</a></li>
<li><a href="http://tools.libove.org/generators/r/259/">Lesser Major Wrists Item</a></li>
<li><a href="http://tools.libove.org/generators/r/260/">Greater Major Wrists Item</a></li>
<li><a href="http://tools.libove.org/generators/r/199/">Lesser Major Slotless Item</a></li>
<li><a href="http://tools.libove.org/generators/r/200/">Greater Major Slotless Item</a></li>
</ul>
<h3>Random Item Type</h3>
<ul>
<li><a href="http://tools.libove.org/generators/r/268/">Lesser Major Wondrous Item</a></li>
<li><a href="http://tools.libove.org/generators/r/269/">Greater Major Wondrous Item</a></li>
</ul>
<p> </p>

]]></description>
    </item>
    <item>
      <guid>https://blog.libove.org/posts/2016-09-12-moved-to-wordpress/</guid>
      <title>Moved to WordPress</title>
      <link>https://blog.libove.org/posts/2016-09-12-moved-to-wordpress/</link>
      <pubDate>Mon, 12 Sep 2016 20:20:08 +0000</pubDate>
      <description><![CDATA[<p>After using a hand-crafted blog for almost 2 years, I've decided to change to WordPress.</p>
<p>I did this mainly out of comfort, as the old software only provided very basic features.</p>
<p>The old post will be transfered into this new set up eventually.</p>

]]></description>
    </item>
  </channel>
</rss>