<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Dr Yegor Stepanov's homepage</title><link>https://ystepanoff.net/</link><description>Recent content on Dr Yegor Stepanov's homepage</description><generator>Hugo -- gohugo.io</generator><language>en</language><managingEditor>ystepanoff@icloud.com (Yegor Stepanov)</managingEditor><webMaster>ystepanoff@icloud.com (Yegor Stepanov)</webMaster><copyright>Yegor Stepanov (CC BY 4.0)</copyright><lastBuildDate>Mon, 10 Nov 2025</lastBuildDate><atom:link href="https://ystepanoff.net/index.xml" rel="self" type="application/rss+xml"/><item><title>ParaGopher: retro-style game in Go</title><link>https://ystepanoff.net/blog/2025-01-31-paragopher/</link><pubDate>Fri, 31 Jan 2025</pubDate><author>ystepanoff@icloud.com (Yegor Stepanov)</author><guid>https://ystepanoff.net/blog/2025-01-31-paragopher/</guid><description>&amp;lt;div class=&amp;#34;block&amp;#34;&amp;gt;
&amp;lt;img src=&amp;#34;https://ystepanoff.net/projects/images/ParaGopher.png&amp;#34; alt=&amp;#34;ParaGopher&amp;#34; /&amp;gt;
I played
&amp;lt;a href=&amp;#34;https://www.retrogames.cz/play_616-DOS.php&amp;#34; target=&amp;#34;_blank&amp;#34;&amp;gt;Paratrooper&amp;lt;/a&amp;gt; a ton back when I was a kid, and
stumbling across &amp;lt;a href=&amp;#34;https://ebitengine.org&amp;#34; target=&amp;#34;_blank&amp;#34;&amp;gt;Ebitengine&amp;lt;/a&amp;gt;—a super straightforward 2D game
engine—brought all those memories rushing back. So I went ahead and made my own nostalgia-fueled
&amp;lt;a href=&amp;#34;https://github.com/ystepanoff/ParaGopher&amp;#34; target=&amp;#34;_blank&amp;#34;&amp;gt;version&amp;lt;/a&amp;gt;, and it only took a couple of hours to
get a basic prototype going! Now I’ve jotted down a few notes on the simple maths behind ParaGopher for anyone
curious.
&amp;lt;h4&amp;gt;Rotating and drawing the turret barrel&amp;lt;/h4&amp;gt;
When drawing the turret barrel, the code rotates it around its center before positioning it on the screen:
&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;op.GeoM.Translate(-barrelW/2.0, -barrelH/2.0) // Move the barrel origin to its center
op.GeoM.Rotate(g.barrelAngle * math.Pi / 180)
op.GeoM.Translate(centerX, centerY)&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;h4&amp;gt;Finding the bullet&amp;#39;s spawn position (tip of the barrel)&amp;lt;/h4&amp;gt;
When you press shoot, the game creates a bullet at the tip of the barrel. The code first identifies a local offset in
the barrel’s coordinate system—something like:
&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;localTipX := width / 2 // half of the base width
localTipY := width / 12&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
Then it rotates these local coordinates by the turret’s angle to get the world position of the tip:
&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;dx := float64(localTipX - width/2)
dy := float64(localTipY - width/2)
rx := float32(dx*math.Cos(angleRadians) - dy*math.Sin(angleRadians))
ry := float32(dx*math.Sin(angleRadians) + dy*math.Cos(angleRadians))
tipX := barrelCircleX + rx
tipY := barrelCircleY + ry&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
Here &amp;lt;code&amp;gt;(dx, dy)&amp;lt;/code&amp;gt; is the local offset from the pivot. The standard 2D rotation formulae are $$
\left.\begin{array}{l} x&amp;#39; = x\cos(\theta) - y\sin(\theta), \\ y&amp;#39; = x\sin(\theta) + y\cos(\theta), \end{array}\right\}
$$ where $\theta$ is the rotation angle in radians.
&amp;lt;h4&amp;gt;Computing bullet&amp;#39;s velocity components&amp;lt;/h4&amp;gt;
Once the bullet&amp;#39;s initial position &amp;lt;code&amp;gt;(tipX, tipY)&amp;lt;/code&amp;gt; is known, its velocity &amp;lt;code&amp;gt;(vx, vy)&amp;lt;/code&amp;gt;
depends on the turret&amp;#39;s angle and a speed constant:
&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;realAngleRadians := (90.0 - g.barrelAngle) * math.Pi / 180.0
vx := float32(config.BulletSpeed * math.Cos(realAngleRadians))
vy := -float32(config.BulletSpeed * math.Sin(realAngleRadians))&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
Notice the &amp;lt;code&amp;gt;(90.0 - barrelAngle)&amp;lt;/code&amp;gt; logic. This is because in normal world maths angle $0$ means pointing
along the positive $x$-axis but here it is actually &amp;#39;straight up&amp;#39;, so we&amp;#39;re shifting the frame of reference so that
$0$ means &amp;lt;b&amp;gt;up&amp;lt;/b&amp;gt; in the game world. Finally, the screen&amp;#39;s $y$-axis is growing downwards, hence the negative sign in
front of the expression for &amp;lt;code&amp;gt;vy&amp;lt;/code&amp;gt;.
&amp;lt;h4&amp;gt;Updating bullet&amp;#39;s position (basic kinematics)&amp;lt;/h4&amp;gt;
On every frame, each bullet’s position is updated by adding the velocity:
&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;b.x += b.vx
b.y += b.vy&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
This is effectively $$ \left.\begin{array}{l} x \leftarrow x + v_x, \\ y \leftarrow y + v_y \end{array}\right\} $$
(which is the simplest form of motion at a constant velocity). If a bullet goes off-screen or hits a target, it’s
removed from the game.
&amp;lt;h4&amp;gt;Collision checks&amp;lt;/h4&amp;gt;
The code for detecting collisions just checks if the two rectangles—one around the bullet, one around the
target—overlap. If they do, the bullet is marked as hitting the target. That’s pretty much the whole idea—after that,
it’s just pressing buttons with your fingers!
&amp;lt;/div&amp;gt;</description><content:encoded><![CDATA[
<div class="block">
  <img src="https://ystepanoff.net/projects/images/ParaGopher.png" alt="ParaGopher" />
 I played
  <a href="https://www.retrogames.cz/play_616-DOS.php" target="_blank">Paratrooper</a> a ton back when I was a kid, and
  stumbling across <a href="https://ebitengine.org" target="_blank">Ebitengine</a>—a super straightforward 2D game
  engine—brought all those memories rushing back. So I went ahead and made my own nostalgia-fueled
  <a href="https://github.com/ystepanoff/ParaGopher" target="_blank">version</a>, and it only took a couple of hours to
  get a basic prototype going! Now I’ve jotted down a few notes on the simple maths behind ParaGopher for anyone
  curious.

  <h4>Rotating and drawing the turret barrel</h4>
  When drawing the turret barrel, the code rotates it around its center before positioning it on the screen:

  <pre><code>op.GeoM.Translate(-barrelW/2.0, -barrelH/2.0) // Move the barrel origin to its center
op.GeoM.Rotate(g.barrelAngle * math.Pi / 180)
op.GeoM.Translate(centerX, centerY)</code></pre>

  <h4>Finding the bullet's spawn position (tip of the barrel)</h4>
  When you press shoot, the game creates a bullet at the tip of the barrel. The code first identifies a local offset in
  the barrel’s coordinate system—something like:
  <pre><code>localTipX := width / 2 // half of the base width
localTipY := width / 12</code></pre>
  Then it rotates these local coordinates by the turret’s angle to get the world position of the tip:
  <pre><code>dx := float64(localTipX - width/2)
dy := float64(localTipY - width/2)

rx := float32(dx*math.Cos(angleRadians) - dy*math.Sin(angleRadians))
ry := float32(dx*math.Sin(angleRadians) + dy*math.Cos(angleRadians))

tipX := barrelCircleX + rx
tipY := barrelCircleY + ry</code></pre>
  Here <code>(dx, dy)</code> is the local offset from the pivot. The standard 2D rotation formulae are $$
  \left.\begin{array}{l} x' = x\cos(\theta) - y\sin(\theta), \\ y' = x\sin(\theta) + y\cos(\theta), \end{array}\right\}
  $$ where $\theta$ is the rotation angle in radians.

  <h4>Computing bullet's velocity components</h4>

  Once the bullet's initial position <code>(tipX, tipY)</code> is known, its velocity <code>(vx, vy)</code>
  depends on the turret's angle and a speed constant:

  <pre><code>realAngleRadians := (90.0 - g.barrelAngle) * math.Pi / 180.0
vx := float32(config.BulletSpeed * math.Cos(realAngleRadians))
vy := -float32(config.BulletSpeed * math.Sin(realAngleRadians))</code></pre>

  Notice the <code>(90.0 - barrelAngle)</code> logic. This is because in normal world maths angle $0$ means pointing
  along the positive $x$-axis but here it is actually 'straight up', so we're shifting the frame of reference so that
  $0$ means <b>up</b> in the game world. Finally, the screen's $y$-axis is growing downwards, hence the negative sign in
  front of the expression for <code>vy</code>.

  <h4>Updating bullet's position (basic kinematics)</h4>
  On every frame, each bullet’s position is updated by adding the velocity:
  <pre><code>b.x += b.vx
b.y += b.vy</code></pre>
  This is effectively $$ \left.\begin{array}{l} x \leftarrow x + v_x, \\ y \leftarrow y + v_y \end{array}\right\} $$
  (which is the simplest form of motion at a constant velocity). If a bullet goes off-screen or hits a target, it’s
  removed from the game.

  <h4>Collision checks</h4>

  The code for detecting collisions just checks if the two rectangles—one around the bullet, one around the
  target—overlap. If they do, the bullet is marked as hitting the target. That’s pretty much the whole idea—after that,
  it’s just pressing buttons with your fingers!
</div>
]]></content:encoded></item><item><title>New kittens!</title><link>https://ystepanoff.net/blog/2025-03-02-kittens-born/</link><pubDate>Sun, 02 Mar 2025</pubDate><author>ystepanoff@icloud.com (Yegor Stepanov)</author><guid>https://ystepanoff.net/blog/2025-03-02-kittens-born/</guid><description>&amp;lt;div class=&amp;#34;block&amp;#34;&amp;gt;
&amp;lt;img src=&amp;#34;https://ystepanoff.net/blog/images/2025-03-20-kittens-born/kittens.png&amp;#34; alt=&amp;#34;Lyra with kittens&amp;#34; /&amp;gt;
&amp;lt;/div&amp;gt;</description><content:encoded>
&lt;div class="block">
  &lt;img src="https://ystepanoff.net/blog/images/2025-03-20-kittens-born/kittens.png" alt="Lyra with kittens" />

&lt;/div>
</content:encoded></item><item><title>Ordet</title><link>https://ystepanoff.net/blog/2025-06-06-ordet/</link><pubDate>Fri, 06 Jun 2025</pubDate><author>ystepanoff@icloud.com (Yegor Stepanov)</author><guid>https://ystepanoff.net/blog/2025-06-06-ordet/</guid><description>&amp;lt;div class=&amp;#34;block&amp;#34;&amp;gt;
&amp;lt;p
&amp;gt;I recently rewatched Carl Theodor Dreyer&amp;#39;s &amp;#34;Ordet&amp;#34; and suddenly got why this film feels timeless. On one hand, it&amp;#39;s
a thoughtful exploration of faith, doubt, and how they mix inside people’s minds. On the other, it’s simply a
powerful story about a Danish family struggling with religious differences and personal conflicts. All of this
gently pulls us into a quiet yet deeply intense reflection on miracles.&amp;lt;/p
&amp;gt;
&amp;lt;p
&amp;gt;Throughout the film, Dreyer beautifully captures how fragile our certainties can be. Characters face tough life
situations, and everything—from the slow dialogues to the minimalist visuals—builds a quiet tension, making you feel
something truly significant is about to happen.&amp;lt;/p
&amp;gt;
&amp;lt;p
&amp;gt;The climax blends rationality with something mystical (perhaps even to the point of frustration—yet that’s exactly
what makes it brilliant). In the final scenes, something occurs that&amp;#39;s interpreted differently by everyone: some see
a moment of spiritual awakening, others view it as a symbol of accepting fate. It’s this ambiguity that makes the
ending of &amp;#34;Ordet&amp;#34; incredibly impactful and memorable.&amp;lt;/p
&amp;gt;
&amp;lt;/div&amp;gt;</description><content:encoded><![CDATA[
<div class="block">
  <p
    >I recently rewatched Carl Theodor Dreyer's "Ordet" and suddenly got why this film feels timeless. On one hand, it's
    a thoughtful exploration of faith, doubt, and how they mix inside people’s minds. On the other, it’s simply a
    powerful story about a Danish family struggling with religious differences and personal conflicts. All of this
    gently pulls us into a quiet yet deeply intense reflection on miracles.</p
  >

  <p
    >Throughout the film, Dreyer beautifully captures how fragile our certainties can be. Characters face tough life
    situations, and everything—from the slow dialogues to the minimalist visuals—builds a quiet tension, making you feel
    something truly significant is about to happen.</p
  >

  <p
    >The climax blends rationality with something mystical (perhaps even to the point of frustration—yet that’s exactly
    what makes it brilliant). In the final scenes, something occurs that's interpreted differently by everyone: some see
    a moment of spiritual awakening, others view it as a symbol of accepting fate. It’s this ambiguity that makes the
    ending of "Ordet" incredibly impactful and memorable.</p
  >
</div>
]]></content:encoded></item><item><title>The Lost Spark: How Modernity Flattened Real and Imaginary</title><link>https://ystepanoff.net/blog/2025-11-10-the-lost-spark/</link><pubDate>Mon, 10 Nov 2025</pubDate><author>ystepanoff@icloud.com (Yegor Stepanov)</author><guid>https://ystepanoff.net/blog/2025-11-10-the-lost-spark/</guid><description>&amp;lt;div class=&amp;#34;block&amp;#34;&amp;gt;
&amp;lt;p&amp;gt;Perhaps it was the &amp;#34;enlightenment&amp;#34; of the eighteenth century, or maybe the French revolution more specifically, that
kicked off this whole idea of modernity. That sudden postulate that the world must be seen as it really is, stripped
of the imaginary, the mystical, the decorative, all those things that don&amp;#39;t quite fit into neat little boxes. And that is
fine to a point, you know, but I think where humanity went wrong was in taking it all too seriously—trying to draw a thick
line between black and white, between the real and the imaginary, the serious and the frivolous. Somewhere along the way, we
decided that for whatever perverse reason both just cannot coexist—it has to be one or the other, with the priority given
to the real and serious.&amp;lt;/p&amp;gt;</description><content:encoded><![CDATA[
<div class="block">
  <p>Perhaps it was the "enlightenment" of the eighteenth century, or maybe the French revolution more specifically, that 
  kicked off this whole idea of modernity. That sudden postulate that the world must be seen as it really is, stripped
  of the imaginary, the mystical, the decorative, all those things that don't quite fit into neat little boxes. And that is 
  fine to a point, you know, but I think where humanity went wrong was in taking it all too seriously—trying to draw a thick
  line between black and white, between the real and the imaginary, the serious and the frivolous. Somewhere along the way, we 
  decided that for whatever perverse reason both just cannot coexist—it has to be one or the other, with the priority given 
  to the real and serious.</p>

  <img src="https://ystepanoff.net/blog/images/2025-11-10-The-Lost-Spark/presidents.png" alt="Rugby Club presidents" />


  <p>Just yesterday, I found myself at a rugby game (as you do on a Sunday), and on the clubhouse wall there was a row of 
  portraits—former presidents of the club or something like that. A whole sequence of faces, each frozen in their own 
  time, each a small artifact of local history. And as I looked at them, I couldn't help but think about that same divide 
  between the real and the imaginary, and in particular what happens when we pretend one can exist without the other! You 
  look at the older photos and there’s something going on there. The faces have a bit of mystery about them—like the man 
  in the middle row on the left. You just want to know who he was, what his story might have been. The photo isn't trying 
  too hard, and that's exactly why it works. His photograph  carries a story. It is imperfect, textured, <i>human</i>.</p>

  <p>Then your eyes move to the most recent one, at the bottom. The guy is standing against a white background, all crisp 
  and tidy, like something you'd get for a staff badge at a supermarket. And suddenly there's nothing left to wonder about.
  It's just some geeza in a suit, for all I care he might have worked at a local Sainsbury's.</p>

  </p>That's what happens, I think, when you strip everything down to what is "real". You lose the spark. You lose the sense of play. 
  And in chasing after that clean, serious version of the world, you accidentally flatten it, until there's nothing left but 
  the surface.</p>
</div>
]]></content:encoded></item></channel></rss>