[{"content":"While building Vulcard for the Game Programming Lab at ETH Zurich, we accumulated dozens of individual sprite PNGs for icons and UI elements. Loading each as a separate texture works fine early on. It\u0026rsquo;s not how you want to ship, though. Every SpriteBatch.Draw call that switches textures flushes the GPU batch, and 60 sprites can easily mean 60 separate draw calls per frame. The fix is a texture atlas: one large texture containing everything, so the batch stays intact.\nWe have already been using MLEM which supports spritesheets in the form of its DataTextureAtlas. However, images have to be manually merged into a single file and documented in a .atlas metadata file. To automate this process I built Vulcard.AtlasPacker, a content pipeline extension that reads a plain-text .atlaspack file and produces a packed texture plus the companion .atlas metadata file at content build time.\nThis post walks through installation, the manifest format, processor options, and how to load the result at runtime using MLEM\u0026rsquo;s DataTextureAtlas.\nWhy Do Texture Atlases Matter in MonoGame? MonoGame\u0026rsquo;s SpriteBatch batches draw calls, but only for consecutive draws that share the same texture. Switch to a different Texture2D and the current batch flushes to the GPU immediately. With many small sprites each living in their own file, that flush happens constantly. Packing everything into one atlas means every sprite draw shares the same texture, so the entire frame goes through as a single batch.\nHow Does AtlasPacker Fit into the Content Pipeline? AtlasPacker is a standard MonoGame content pipeline extension: a DLL that MonoGame\u0026rsquo;s content builder (mgcb) loads at build time. You write a .atlaspack manifest listing which sprites to include, run dotnet build, and the pipeline produces two output files:\nA .xnb texture (or optional .png) containing all the packed sprites A .atlas text file mapping sprite names to pixel rectangles in the atlas At runtime you load those two files with MLEM\u0026rsquo;s DataTextureAtlas and look up sprites by name. Adding a new sprite means editing one text file. That\u0026rsquo;s it.\nHere is what the packed atlas looks like for Vulcard\u0026rsquo;s icon sprites — 37 sprites merged into a single texture:\nInstallation dotnet add package Vulcard.AtlasPacker The package\u0026rsquo;s MSBuild targets file auto-registers the pipeline assembly with MonoGame.Content.Builder.Task. For MSBuild-driven builds, that\u0026rsquo;s all you need. No manual .mgcb edits required.\nIf you build content with the MGCB editor or call dotnet-mgcb directly, add the reference manually in your .mgcb file:\n/reference:path/to/Vulcard.AtlasPacker.dll The property $(VulcardAtlasPacker_AssemblyPath) gives you the exact path once the package is restored.\nHow Do You Write the Manifest? Create a file with the .atlaspack extension next to your sprites (or anywhere under your content root). Set its Importer to Atlas Packer Importer and its Processor to Atlas Packer Processor in the MGCB editor.\nThe manifest is plain text: one glob pattern per line, with optional per-sprite annotations. Lines starting with # are comments.\n# Global default: pack every sprite below into a 64×64 slot. size 64 # A glob: picks up every PNG in the Effects subfolder. Effects/* # Per-sprite overrides. button.png size 128 128 sprite.png rect 10 5 48 48 sprite.png rect 10 5 48 48 size 128 128 # Opt out of the global size for a specific sprite. icon.png size 0 0 Annotations Every line can carry size and/or rect annotations after the glob pattern. They can appear in either order.\nSyntax Meaning size N Square target slot. Source is centred; transparent padding fills the gap. size W H Rectangular slot size 0 0 Disable the global default for this entry rect S Source crop (0, 0, S, S) rect O S Source crop (O, O, S, S) rect X Y W H Explicit source crop rectangle When no annotations are given and there\u0026rsquo;s no global default, AtlasPacker auto-trims the sprite. It scans for non-transparent pixels and uses their bounding box as the source region. A 512x512 PNG with a 64x64 icon in the center gets packed at 64x64, not 512x512.\nYou can also set global defaults at the top of the manifest that apply to everything below:\n# Global rect for all subsequent entries. rect 8 8 48 48 # Clear the global rect (mirrors \u0026#34;size 0 0\u0026#34;). rect 0 0 0 0 Globs are expanded using Microsoft.Extensions.FileSystemGlobbing, so **/*.png picks up all PNGs recursively, Effects/* picks up only the direct children of Effects/, and so on. If the same file matches multiple patterns, only the first occurrence (and its annotations) is kept.\nWhat Processor Options Are Available? Three properties are configurable from the MGCB editor or your .mgcb file:\nProperty Default Notes Padding 0 Pixel gutter around each sprite in the atlas PowerOfTwo true Round atlas dimensions up to the next power of two OutputAsPng false Also write a .png alongside the .xnb Padding adds symmetric gutters between sprites, which prevents texture bleeding when bilinear filtering samples a neighboring sprite\u0026rsquo;s edge. Even 1 or 2 pixels is usually enough; zero works fine if you\u0026rsquo;re using nearest-neighbor filtering or don\u0026rsquo;t see any bleeding.\nPowerOfTwo was a hard requirement on older GPUs. Modern hardware handles non-power-of-two textures without issue, so you can disable it to use the exact packed dimensions and shave a bit of memory.\nOutputAsPng writes a raw .png alongside the .xnb. This is useful both for inspecting the packed layout during development and for shipping — on Vulcard we use the PNG output directly at runtime, since it\u0026rsquo;s smaller than the equivalent .xnb.\nHow Does the Packing Work? AtlasPacker uses a binary-tree bin packing algorithm, based on the approach described by Jake Gordon. Sprites are sorted by area descending, so the largest sprites get placed first. The tree tracks free rectangles in the atlas, splitting each free region into a right strip and a bottom strip as sprites are inserted. The root node grows right or down to accommodate sprites that exceed the current bounds.\nDoes this guarantee a globally optimal layout? No, and optimal 2D bin packing is NP-hard anyway. In practice, though, it wastes very little space when sprites have similar sizes, which is the common case for game sprite sheets.\nThe processor works in two passes. Pass 1 loads each source image just long enough to read its trim bounds, then discards it. Pass 2 composites images into the atlas one at a time, holding only one source in memory at once. For large sprite sets, peak memory stays low regardless of how many sprites you\u0026rsquo;re packing.\nName collisions AtlasPacker uses the filename without extension as the atlas key. Two files named attack.png in different folders would produce a duplicate key. The processor detects this and throws an error listing the conflicting paths. What Does the Output Atlas File Look Like? The processor writes a \u0026lt;name\u0026gt;.atlas text file next to the built texture. Its format is compatible with MLEM\u0026rsquo;s DataTextureAtlas (MLEM documentation):\n# format xnb iconattack loc 0 0 64 64 icondefend loc 64 0 64 64 button loc 0 64 128 128 The # format xnb header (or # format png when OutputAsPng is true) is ignored by MLEM\u0026rsquo;s parser, but lets your own loading code detect which texture type to read. The format itself is dead simple: sprite name on one line, loc X Y W H on the next. You could write a standalone loader in under ten lines of C# if you\u0026rsquo;d rather not take the MLEM dependency.\nLoading at Runtime with MLEM Atlas keys are filenames without their extension — iconattack.png becomes \u0026quot;iconattack\u0026quot; in the dictionary lookup.\nusing MLEM.Data; using MLEM.Textures; var texture = content.Load\u0026lt;Texture2D\u0026gt;(\u0026#34;Sprites/Icons/icons\u0026#34;); DataTextureAtlas atlas = DataTextureAtlas.LoadAtlasData( new TextureRegion(texture), content, \u0026#34;Sprites/Icons/icons.atlas\u0026#34;); TextureRegion region = atlas[\u0026#34;iconattack\u0026#34;]; spriteBatch.Draw(region, position, Color.White); The .atlas file starts with a # format xnb or # format png header so your loader knows which texture type to expect. A small helper handles both cases:\npublic static DataTextureAtlas LoadTextureAtlas( this ContentManager content, GraphicsDevice graphicsDevice, string name) { string format = \u0026#34;xnb\u0026#34;; using (var reader = new StreamReader( TitleContainer.OpenStream(content.RootDirectory + \u0026#34;/\u0026#34; + name + \u0026#34;.atlas\u0026#34;))) { var first = reader.ReadLine() ?? \u0026#34;\u0026#34;; if (first.StartsWith(\u0026#34;# format \u0026#34;)) format = first[\u0026#34;# format \u0026#34;.Length..].Trim(); } Texture2D texture; if (format == \u0026#34;png\u0026#34;) { using var stream = TitleContainer.OpenStream(content.RootDirectory + \u0026#34;/\u0026#34; + name + \u0026#34;.png\u0026#34;); texture = Texture2D.FromStream(graphicsDevice, stream).PremultipliedCopy(); } else { texture = content.Load\u0026lt;Texture2D\u0026gt;(name); } return DataTextureAtlas.LoadAtlasData(new TextureRegion(texture), content, name + \u0026#34;.atlas\u0026#34;); } TextureRegion is MLEM\u0026rsquo;s rectangle-within-a-texture type. Sprite lookups on the loaded atlas are dictionary reads: O(1), allocation-free.\nFrequently Asked Questions Does AtlasPacker work with the MGCB editor? Yes. Open the MGCB editor, add your .atlaspack file, and set the Importer to Atlas Packer Importer and the Processor to Atlas Packer Processor. If the assembly isn\u0026rsquo;t detected automatically, add a /reference: line pointing to Vulcard.AtlasPacker.dll. Its path is exposed via $(VulcardAtlasPacker_AssemblyPath) in MSBuild once the package is restored.\nCan I use AtlasPacker without MLEM? Yes. The .atlas format is plain text: sprite name on one line, loc X Y W H on the next. You can write your own loader in a few lines. MLEM\u0026rsquo;s DataTextureAtlas is the primary target, but it\u0026rsquo;s not required.\nWhat happens to fully transparent sprites? The processor logs a warning and substitutes a 1x1 fallback slot rather than crashing. A fully transparent image is almost always a mistake — a missing asset, a wrong path — and a warning is a lot easier to track down than a mysteriously absent sprite.\nWrapping Up AtlasPacker is a small tool, but it quietly fixed something that was wasting time on Vulcard. Adding a sprite is now one line in a text file, and the build handles the rest. If you\u0026rsquo;re starting a MonoGame project with more than a handful of sprites, get this set up early.\ndotnet add package Vulcard.AtlasPacker If you\u0026rsquo;re also setting up cross-platform builds or Steam integration for your MonoGame project, the guide to bundling a MonoGame game for multiple platforms covers the full MSBuild and packaging setup.\nThe source is on GitHub under the MIT licence.\n","permalink":"https://ateon.ch/posts/atlas-packer/","summary":"\u003cp\u003eWhile building Vulcard for the \u003ca href=\"https://gtc.inf.ethz.ch/education/game-programming-laboratory/previous-years/2025.html\"\u003eGame Programming Lab\u003c/a\u003e at ETH Zurich, we accumulated dozens of individual sprite PNGs for icons and UI elements. Loading each as a separate texture works fine early on. It\u0026rsquo;s not how you want to ship, though. Every \u003ccode\u003eSpriteBatch.Draw\u003c/code\u003e call that switches textures flushes the GPU batch, and 60 sprites can easily mean 60 separate draw calls per frame. The fix is a texture atlas: one large texture containing everything, so the batch stays intact.\u003c/p\u003e\n\u003cp\u003eWe have already been using \u003ca href=\"https://mlem.ellpeck.de/\"\u003eMLEM\u003c/a\u003e which supports spritesheets in the form of its \u003ccode\u003eDataTextureAtlas\u003c/code\u003e. However, images have to be manually merged into a single file and documented in a \u003ccode\u003e.atlas\u003c/code\u003e metadata file.\nTo automate this process I built \u003cstrong\u003e\u003ca href=\"https://www.nuget.org/packages/Vulcard.AtlasPacker\"\u003eVulcard.AtlasPacker\u003c/a\u003e\u003c/strong\u003e, a content pipeline extension that reads a plain-text \u003ccode\u003e.atlaspack\u003c/code\u003e file and produces a packed texture plus the companion \u003ccode\u003e.atlas\u003c/code\u003e metadata file at content build time.\u003c/p\u003e\n\u003cp\u003eThis post walks through installation, the manifest format, processor options, and how to load the result at runtime using MLEM\u0026rsquo;s \u003ccode\u003eDataTextureAtlas\u003c/code\u003e.\u003c/p\u003e","title":"Packing Sprites into a Texture Atlas in MonoGame"},{"content":"When a satellite sends telemetry data to a ground station, both sides need to agree on exactly how that data is structured in binary. The same goes for network protocols, aircraft systems, and anything else where machines exchange precisely formatted messages. Get the encoding wrong and you get garbage. Get the decoding wrong and you may silently recover incorrect data.\nWhy Does ASN.1 Need Formal Verification? In safety-critical systems, bugs in communication protocol implementations can produce incorrect encodings or silent decoding errors that a finite test suite may not catch. ASN.1 (Abstract Syntax Notation One) is an international standard for describing data structures independently of any programming language or platform.1 You define your types once (integers, strings, sequences, enumerations). A separate set of encoding rules then determines how those types map to bytes on the wire.\nA simple definition looks like this:\nTemperature ::= INTEGER (0..100) This says Temperature is an integer constrained to the range 0–100. The encoding rules then pack that value into as few bits as possible.\nThe encoding rules used in this thesis are uPER (Unaligned Packed Encoding Rules). uPER is compact, which makes it a natural fit for embedded systems and satellite communication where bandwidth is limited. The European Space Agency uses ASN.1 with uPER for telemetry and telecommand data between spacecraft and ground stations.\nWhat Is ASN1SCC? Writing encoders and decoders by hand for every type in a specification is tedious and error-prone. The ESA developed ASN1SCC to automate this: an open-source compiler that takes an ASN.1 specification as input and generates the corresponding encoding and decoding code.2 It supports C, Ada, and Scala, with a Python backend under active development.\nGiven the Temperature definition above, ASN1SCC generates a Python class with encode and decode methods. Using them looks roughly like this:\nencoder = UPEREncoder.of_size(1) val = Temperature(42) val.encode(encoder) data = encoder.get_bitstream_buffer() decoder = UPERDecoder.from_buffer(data) result = Temperature.decode(decoder) # Is result == val? The question my thesis set out to answer: can we prove that result == val holds for every valid input, not just the ones we happened to test?\nWhy Testing Alone Isn\u0026rsquo;t Enough Tests are the standard answer. Write inputs, check outputs, add edge cases. Done carefully, this catches a lot of bugs.\nBut tests only cover the cases you thought to write. A Temperature value of 42 passes. What about 127? What about 128, where the bit-packing crosses a byte boundary? What about the exact edges of the constraint range? What about a complex nested structure with a dozen fields, where the encoder for each field must leave the stream in exactly the right state for the next one?\nAutomated testing variants Testing has more sophisticated variants. Symbolic execution (e.g., KLEE) treats inputs as symbolic variables and automatically generates concrete inputs to cover different code paths, which is far more systematic than writing tests by hand. Fuzzing generates large volumes of random or mutation-based inputs and can find bugs that deterministic test suites miss entirely.\nBoth techniques close some of the coverage gap. But they still explore a finite set of execution paths. For programs with unbounded inputs or complex loop structures, neither can guarantee that every case has been covered. Formal verification closes that gap.\nFormal verification is a different game. You write a mathematical statement about what the code must do for all inputs, and a tool proves or disproves it automatically. No enumeration of cases.\nWhat I Set Out to Prove The property I focused on is round-trip correctness:\nFor all valid inputs, decoding the output of an encoder recovers the original value.\nFormally: $\\forall x . decode(encode(x)) = x$\nThe proof is scoped to valid inputs: the precondition requires constraint-satisfying values on the encoder side and a well-formed buffer on the decoder side. It says nothing about how the decoder handles malformed input from an untrusted source. But within that scope it gives a precise, unconditional correctness statement: the encoder cannot silently corrupt a value, and the decoder cannot misread what the encoder wrote.\nNagini: Formal Verification for Python The verifier I used is Nagini, a static analysis tool for Python developed by Dr. Marco Eilers at ETH Zurich.3 Nagini lets you annotate Python functions with preconditions and postconditions, then uses an SMT solver to prove those statements hold for every possible execution. Under the hood it translates Python to Viper, an intermediate verification language.4\nAn annotated encode function looks like this:\ndef encode(self, codec: UPEREncoder) -\u0026gt; None: Requires(self.is_constraint_valid()) Ensures(codec.segments == Old(codec.segments) + segments_of(self)) # ... implementation ... Requires is the precondition: the value being encoded must satisfy its constraints. Ensures is the postcondition: the encoder\u0026rsquo;s state has been extended by exactly the segments representing this value. Once Nagini accepts this, no test needs to cover that contract. It holds unconditionally.\nThe Segment Abstraction Encoding writes bits into a shared byte buffer. A single write can span two bytes, and you need to reason precisely about which bits changed and which didn\u0026rsquo;t. Tracking this at the bit level throughout the whole proof would be unmanageable.\nThe approach I used is a three-layer architecture. The bottom two layers handle actual bit manipulation: individual bits within a byte, then multi-bit writes across the full buffer. Above those sits a segment abstraction used purely for verification. Instead of tracking which bits changed, each write is recorded as a (value, length) pair called a segment.\ngraph TB A[\"Segment abstractionEncoders and decoders reason at this level\"] B[\"Byte-sequence layerTracks bit writes across the buffer\"] C[\"Bit-level layerIndividual bit read/write within a byte\"] A --- B --- C Once the bottom layers are proved correct, the segment abstraction guarantees that the sequence of segments corresponds exactly to the buffer contents. Encoder and decoder proofs then work entirely with segments, without bit arithmetic. That separation is what makes the round-trip proofs tractable, and it distinguishes this approach from the bit-list intermediate representation used in the prior Scala verification work.5\nI\u0026rsquo;ll cover the segment abstraction and compositional proof structure in more detail in a follow-up post.\nWhat Was Formally Verified The first component verified was BitStream, the core data structure shared by all generated codecs. The verification establishes absence of runtime errors (index out-of-bounds, overflow) and full functional correctness of all read and write operations: each written value is correctly retrieved by a subsequent read, and previously written data is unchanged. Everything else rests on this.\nBuilding on BitStream, six ASN.1 types were proved to have round-trip correctness under uPER:\nBOOLEAN NULL ENUMERATED INTEGER (constrained range) OCTET STRING (fixed size) SEQUENCE (with fixed-size, non-optional fields) Types like SEQUENCE OF, CHOICE, BIT STRING, and REAL were not verified. Most follow the same proof pattern and are primarily a matter of implementing type-specific auxiliary functions. REAL is the exception: it requires further development in Nagini\u0026rsquo;s floating-point support before it can be tackled at the codec level.\nThe Cost: Annotation Overhead Formal verification is not free. Proofs require writing specifications alongside the implementation. Across the verified runtime files, annotation lines account for 39.9% of the codebase: 1,636 specification lines alongside 2,461 lines of implementation.\nThe distribution is uneven by design. bitstream.py, which establishes the segment abstraction at the byte-sequence level, has more specification than implementation (68% annotation overhead). The encoder and decoder, working at the segment level rather than at the bit level, need far less: 12.6% and 13.5% respectively. The annotation burden concentrates at the foundation, so the higher-level proofs stay comparatively lightweight.\nsegment.py and verification.py consist entirely of specification code with no runtime counterparts; they exist solely to support the proof.\nThe generated data classes sit at 54% specification, since each class needs its own postconditions and helper lemmas. That\u0026rsquo;s the cost of annotating code you didn\u0026rsquo;t write.\nTwo Bugs Found Before Running the Prover Writing formal specifications sometimes finds bugs before the prover even runs. Precisely stating what the code should do exposes gaps between that and what it actually does. Two bugs turned up in the ASN1SCC Python backend this way:\nThe is_constraint_valid check for INTEGER was missing the lower bound of zero, accepting negative values as valid. The is_constraint_valid check for OCTET STRING did not enforce the fixed-size constraint, accepting arrays of any length. Both were caught just from writing the specification, before running a single proof.\nHow I Extended Nagini The verification also required extending Nagini to handle Python features it couldn\u0026rsquo;t verify before:\nbytearray: a mutable heap-allocated type, modelled as a Seq[Int] in Viper with a permission predicate governing access, plus a pure PByteSeq counterpart for use in specifications Shift operators (\u0026lt;\u0026lt; and \u0026gt;\u0026gt;): encoded via integer arithmetic, since SMT integers don\u0026rsquo;t support bitwise shifts directly; left shift by k becomes multiplication by 2^k, resolved through a case distinction over the shift amount Dataclasses: @dataclass-decorated classes with implicit __init__, supporting frozen and non-frozen forms and factory defaults IntEnum: integer-backed enumerations, encoded with boxing/unboxing functions that enforce the set of valid values at the type level Beyond new features, six crashes and three soundness issues in Nagini were identified and reported to the issue tracker, each with a minimal reproducing test case. All were subsequently fixed. One soundness bug was particularly subtle: because a Python integer subclass satisfies A(5) == 5, Nagini was misled into accepting the trivially false assertion assert 2 == 1 as valid, which I found while writing tests that were supposed to fail.\nI\u0026rsquo;ll cover these extensions in more detail in a follow-up post.\nArtifacts and Prior Work The full thesis is available on the completed projects page of the Programming Methodology Group at ETH Zurich. Changes to Nagini have been committed to the Nagini repository directly. ASN1SCC is open source on GitHub.\nThis work builds on a prior project that applied the same round-trip verification approach to ASN1SCC\u0026rsquo;s Scala backend.5 The aim was to show the same correctness class is achievable in Python.\nITU-T, X.680: Information Technology – Abstract Syntax Notation One (ASN.1), 2021. https://www.itu.int/rec/T-REC-X.680/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nG. Mamais, T. Tsiodras, D. Lesens, M. Perrotin, \u0026ldquo;An ASN.1 compiler for embedded/space systems,\u0026rdquo; ERTS 2012, Toulouse, France. https://hal.science/hal-02263447\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nM. Eilers, P. Müller, \u0026ldquo;Nagini: A Static Verifier for Python,\u0026rdquo; Computer Aided Verification (CAV), 2018, pp. 596–603.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nP. Müller, M. Schwerhoff, A. J. Summers, \u0026ldquo;Viper: A Verification Infrastructure for Permission-Based Reasoning,\u0026rdquo; VMCAI, 2016. https://viper.ethz.ch\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nM. Bucev, S. Chassot, S. Felix, F. Schramka, V. Kunčak, \u0026ldquo;Formally Verifiable Generated ASN.1/ACN Encoders and Decoders: A Case Study,\u0026rdquo; arXiv:2412.07235, 2024. https://arxiv.org/abs/2412.07235\u0026#160;\u0026#x21a9;\u0026#xfe0e;\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://ateon.ch/posts/formally-verified-asn1/","summary":"\u003cp\u003eWhen a satellite sends telemetry data to a ground station, both sides need to agree on exactly how that data is structured in binary. The same goes for network protocols, aircraft systems, and anything else where machines exchange precisely formatted messages. Get the encoding wrong and you get garbage. Get the decoding wrong and you may silently recover incorrect data.\u003c/p\u003e","title":"Formally Verified ASN.1 Encoders and Decoders"},{"content":"While developing Vulcard for this year\u0026rsquo;s iteration of the Game Programming Lab at ETH Zurich, we wanted to integrate the Steam API and package the game for multiple platforms. This process came with a few unexpected hurdles.\nIn this post, I\u0026rsquo;ll walk through the complete solution we ended up using. If you\u0026rsquo;re looking to publish a MonoGame project on Steam, this might save you some time.\nWhat\u0026#39;s Monogame? MonoGame is a cross-platform .NET framework for game development. It has for example been used to develop Stardew Valley. General Setup I assume you already have a working .NET 6+ (MonoGame) project and the corresponding SDK installed. You’ll also need a Steamworks developer account to access the Steamworks SDK.\nTo make platform-specific builds easier, define platform detection constants in your .csproj file:\n\u0026lt;PropertyGroup\u0026gt; \u0026lt;IsWindows Condition=\u0026#34;\u0026#39;$(RuntimeIdentifier)\u0026#39; == \u0026#39;win-x64\u0026#39; or (\u0026#39;$(RuntimeIdentifier)\u0026#39; == \u0026#39;\u0026#39; and $([MSBuild]::IsOSPlatform(\u0026#39;Windows\u0026#39;)))\u0026#34;\u0026gt;true\u0026lt;/IsWindows\u0026gt; \u0026lt;IsOSX Condition=\u0026#34;\u0026#39;$(RuntimeIdentifier)\u0026#39; == \u0026#39;osx-x64\u0026#39; or (\u0026#39;$(RuntimeIdentifier)\u0026#39; == \u0026#39;\u0026#39; and $([MSBuild]::IsOSPlatform(\u0026#39;OSX\u0026#39;)))\u0026#34;\u0026gt;true\u0026lt;/IsOSX\u0026gt; \u0026lt;IsLinux Condition=\u0026#34;\u0026#39;$(RuntimeIdentifier)\u0026#39; == \u0026#39;linux-x64\u0026#39; or (\u0026#39;$(RuntimeIdentifier)\u0026#39; == \u0026#39;\u0026#39; and $([MSBuild]::IsOSPlatform(\u0026#39;Linux\u0026#39;)))\u0026#34;\u0026gt;true\u0026lt;/IsLinux\u0026gt; \u0026lt;IsWindows Condition=\u0026#34;\u0026#39;$(IsWindows)\u0026#39; == \u0026#39;\u0026#39;\u0026#34;\u0026gt;false\u0026lt;/IsWindows\u0026gt; \u0026lt;IsOSX Condition=\u0026#34;\u0026#39;$(IsOSX)\u0026#39; == \u0026#39;\u0026#39;\u0026#34;\u0026gt;false\u0026lt;/IsOSX\u0026gt; \u0026lt;IsLinux Condition=\u0026#34;\u0026#39;$(IsLinux)\u0026#39; == \u0026#39;\u0026#39;\u0026#34;\u0026gt;false\u0026lt;/IsLinux\u0026gt; \u0026lt;/PropertyGroup\u0026gt; Add Steamworks Dependencies Download the Facepunch.Steamworks library directly from the GitHub releases page and extract the contents of the net6.0 folder into a new folder in your project root called Steamworks.\nAlso, download the official Steamworks SDK directly from Steamworks. As of writing, SDK version 1.61 works with the latest release of Facepunch.Steamworks. Copy the redistributable binaries (steam_api64.dll, libsteam_api.so, etc.) into the same Steamworks folder.\nYour Steamworks/ folder should now include:\nFacepunch.Steamworks.Posix.deps.json Facepunch.Steamworks.Posix.dll Facepunch.Steamworks.Posix.pdb Facepunch.Steamworks.Posix.xml Facepunch.Steamworks.Win64.deps.json Facepunch.Steamworks.Win64.dll Facepunch.Steamworks.Win64.pdb Facepunch.Steamworks.Win64.xml libsteam_api.dylib libsteam_api.so steam_api64.dll 32-bit Support To support 32-bit Windows systems, you’ll need to include the appropriate 32-bit versions of the Steam libraries as well. Update the .csproj to reference the Facepunch.Steamworks and native libraries for the correct platform:\n\u0026lt;ItemGroup\u0026gt; \u0026lt;!-- Reference Facepunch.Steamworks --\u0026gt; \u0026lt;Reference Include=\u0026#34;Facepunch.Steamworks, Version=2.4.1, Culture=neutral, processorArchitecture=MSIL\u0026#34;\u0026gt; \u0026lt;SpecificVersion\u0026gt;False\u0026lt;/SpecificVersion\u0026gt; \u0026lt;HintPath Condition=\u0026#34;$(IsWindows)\u0026#34;\u0026gt;Steamworks/Facepunch.Steamworks.Win64.dll\u0026lt;/HintPath\u0026gt; \u0026lt;HintPath Condition=\u0026#34;$(IsOSX) or $(IsLinux)\u0026#34;\u0026gt;Steamworks/Facepunch.Steamworks.Posix.dll\u0026lt;/HintPath\u0026gt; \u0026lt;CopyToOutputDirectory\u0026gt;Always\u0026lt;/CopyToOutputDirectory\u0026gt; \u0026lt;/Reference\u0026gt; \u0026lt;!-- Include native Steamworks libaries --\u0026gt; \u0026lt;Content Include=\u0026#34;Steamworks/steam_api64.dll\u0026#34; Condition=\u0026#34;$(IsWindows)\u0026#34;\u0026gt; \u0026lt;Link\u0026gt;steam_api64.dll\u0026lt;/Link\u0026gt; \u0026lt;CopyToOutputDirectory\u0026gt;Always\u0026lt;/CopyToOutputDirectory\u0026gt; \u0026lt;/Content\u0026gt; \u0026lt;Content Include=\u0026#34;Steamworks/libsteam_api.dylib\u0026#34; Condition=\u0026#34;$(IsOSX)\u0026#34;\u0026gt; \u0026lt;Link\u0026gt;Lib/libsteam_api.dylib\u0026lt;/Link\u0026gt; \u0026lt;CopyToOutputDirectory\u0026gt;Always\u0026lt;/CopyToOutputDirectory\u0026gt; \u0026lt;/Content\u0026gt; \u0026lt;Content Include=\u0026#34;Steamworks/libsteam_api.so\u0026#34; Condition=\u0026#34;$(IsLinux)\u0026#34;\u0026gt; \u0026lt;Link\u0026gt;libsteam_api.so\u0026lt;/Link\u0026gt; \u0026lt;CopyToOutputDirectory\u0026gt;Always\u0026lt;/CopyToOutputDirectory\u0026gt; \u0026lt;/Content\u0026gt; \u0026lt;/ItemGroup\u0026gt; libsteam_api.dylib The native steam library for macOS libsteam_api.dylib is copied directly into the Lib folder to conform with the app bundle structure for macOS.\nThis might mess with your setup if you are using macOS for development. If you know of a good way to force NetBeauty to automatically copy the file to the Lib folder during bundling, let me know.\nIf everything is working, you can initialize the Steam API in your code and check the connection.\npublic void Initialize() { try { Steamworks.SteamClient.Init(480, true); var playername = Steamworks.SteamClient.Name; } catch (Exception e) { Debug.WriteLine(\u0026#34;{0}\u0026#34;, e); } } App ID Replace 480 with your actual Steam App ID for a release. Facepunch.Steamworks also doesn\u0026rsquo;t require a steam_appid.txt file. Bundling Windows \u0026amp; Linux Download GameBundle. This neat tool greatly simplifies the bundling process and provides a clean looking folder structure by using NetBeauty internally.\nAnd that is it. You can now use the Steam API and bundle your game for Windows and Linux:\ngamebundle -wl -z --mg # -w: Build for Windows # -l: Build for Linux # -z: Zip output # --mg: Don\u0026#39;t move MonoGame\u0026#39;s native libraries to the Lib folder Note that Linux users will need to add the permissions to execute the game.\nBundling macOS Bundling for macOS is a bit more involved and additionally requires a property list file and a separate icon.\nConvert your existing game icon to the icns format and store the new file under Icon.icns directly in the root. Then create a new file called Info.plist also in the project root.\nAdd the following content to the Info.plist. Replace MyApp with the name of your application and com.example.MyApp with your identifier.\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;!DOCTYPE plist PUBLIC \u0026#34;-//Apple//DTD PLIST 1.0//EN\u0026#34; \u0026#34;http://www.apple.com/DTDs/PropertyList-1.0.dtd\u0026#34;\u0026gt; \u0026lt;plist version=\u0026#34;1.0\u0026#34;\u0026gt; \u0026lt;dict\u0026gt; \u0026lt;key\u0026gt;CFBundleIconFile\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;Icon\u0026lt;/string\u0026gt; \u0026lt;key\u0026gt;CFBundleName\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;MyApp\u0026lt;/string\u0026gt; \u0026lt;key\u0026gt;CFBundleIdentifier\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;com.example.MyApp\u0026lt;/string\u0026gt; \u0026lt;key\u0026gt;CFBundlePackageType\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;APPL\u0026lt;/string\u0026gt; \u0026lt;key\u0026gt;CFBundleVersion\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;1.0\u0026lt;/string\u0026gt; \u0026lt;key\u0026gt;CFBundleShortVersionString\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;1.0\u0026lt;/string\u0026gt; \u0026lt;/dict\u0026gt; \u0026lt;/plist\u0026gt; Finally, adjust your .csproj to include them during the build for macOS:\n\u0026lt;ItemGroup Condition=\u0026#34;$(IsOSX)\u0026#34;\u0026gt; \u0026lt;Content Include=\u0026#34;./Info.plist\u0026#34;\u0026gt; \u0026lt;CopyToOutputDirectory\u0026gt;Always\u0026lt;/CopyToOutputDirectory\u0026gt; \u0026lt;/Content\u0026gt; \u0026lt;Content Include=\u0026#34;./Icon.icns\u0026#34;\u0026gt; \u0026lt;CopyToOutputDirectory\u0026gt;Always\u0026lt;/CopyToOutputDirectory\u0026gt; \u0026lt;/Content\u0026gt; \u0026lt;/ItemGroup\u0026gt; Now you can also bundle your game for macOS again using GameBundle.\ngamebundle -m -bz --mg --nbeauty2 # -m: Bundle for macOS # -b: Output correct structure and create app bundle for macOS # -z: Zip output # --mg: Don\u0026#39;t move MonoGame\u0026#39;s native libraries to the Lib folder # --nbeauty2: Use NetBeauty2 instead of NetCoreBeauty Why --nbeauty2? The .NET version I used (8.0.410) does not have a patched .NET SDK for macOS in NetCoreBeauty.\nNetBeauty2 uses a different setup, which works in that case. However, there are other drawbacks with NetBeauty2.\nSigning \u0026amp; Notarization The setup shown here does not include signing and notarizing the app, which is required by Apple if you want to distribute the app. Conclusion You can now use the Steamworks API in your .NET project and bundle it for Windows, Linux and macOS. Hope this saves you some time.\nSee the github repository for a basic setup that includes project structure, and configuration files.\nNote By the way you can find Vulcard on Steam. ","permalink":"https://ateon.ch/posts/monogame_bundle/","summary":"\u003cp\u003eWhile developing \u003cstrong\u003eVulcard\u003c/strong\u003e for this year\u0026rsquo;s iteration of the \u003ca href=\"https://gtc.inf.ethz.ch/education/game-programming-laboratory/previous-years/2025.html\"\u003eGame Programming Lab\u003c/a\u003e at ETH Zurich, we wanted to integrate the Steam API and package the game for multiple platforms. This process came with a few unexpected hurdles.\u003c/p\u003e\n\u003cp\u003eIn this post, I\u0026rsquo;ll walk through the complete solution we ended up using. If you\u0026rsquo;re looking to publish a MonoGame project on Steam, this might save you some time.\u003c/p\u003e","title":"Bundling a Game Made with MonoGame"},{"content":"Like Polymer before, Lit-Element uses javascript or typescript files for code, templates and styles by default, to enable the use of javascript variables. While this may be useful in some cases, personally I prefer having my style definitions in separate files mainly to benefit from Sass, but also the added benefit of editor assistance like highlighting and autocompletion. Fortunately this can be changed with a bundler like Webpack.\nWhat is Lit? Lit Element is a Web Component library. It adds some useful boilerplate and typescript definitions on top of the Web Component standards. A Web Component itself is simply a custom element like any other default html element. But they encapsulate their code and styling from the rest of the page, which makes them highly modular. In a way their job is similar to classes in object-oriented programming languages.\nMany well known Libraries like Angular or React make heavy use of Web Components.\nPrevious Setup Previously with Polymer I used the polymer-css-loader alongside its requirements to import stylesheets in javascript modules.\nconfig = { ..., module: { rules: [ ..., { test: /\\.css|\\.s(c|a)ss$/, use: [ babel, { loader: \u0026#39;polymer-css-loader\u0026#39;, options: { minify: true, url: false }, }, \u0026#39;extract-loader\u0026#39;, \u0026#39;css-loader\u0026#39;, \u0026#39;resolve-url-loader\u0026#39;,\u0026#39;sass-loader?sourceMap\u0026#39;] }, { test: /\\.(png|jpg|gif|svg)$/, use: [{ loader: \u0026#39;url-loader\u0026#39;, options: { limit: 10 * 1024, outputPath: \u0026#39;assets\u0026#39; } }] }, ] } } There actually exists a continuation of it for lit elements called lit-css-loader. Unfortunately extract-loader seems to be broken in Webpack 5, especially when loading images from Sass files.\nNew Setup Instead the css-loader can now be used on its own to export the stylesheets in the required format.\nconfig = { ..., module: { rules: [ ..., { test: /\\.css|\\.s(c|a)ss$/, use: [ { loader: \u0026#39;css-loader\u0026#39;, options: { esModule: true, exportType: \u0026#34;css-style-sheet\u0026#34;, } }, \u0026#39;resolve-url-loader\u0026#39;, { loader: \u0026#39;sass-loader\u0026#39;, options: { sourceMap: true, } }] }, { test: /\\.(png|jpg|gif|svg)$/, type: \u0026#39;asset\u0026#39;, parser: { dataUrlCondition: { maxSize: 4 * 1024 // 4kb } } generator: { filename: \u0026#39;assets/images/[name].[ext]\u0026#39; } } ] } } Some details about the different loaders used:\nsass-loader is needed to compile sass to pure css. Webpack expects relative paths to be in relation to the root file. To fix this resolve-url-loader re-writes those paths to correctly load files. Then css-loader translates CSS to Javascript. The url-loader to load images has now been replaced by the Asset Module from Webpack 5. Images smaller than 4kb will be inlined, while larger images are stores as a separate file. Hopefully this quick summary will save you some time if you are working with Lit-Elements.\nAdditional remarks As the CSS Module Scripts feature gets deployed to all browsers this setup might become simpler.\n","permalink":"https://ateon.ch/posts/lit-scss-loading/","summary":"\u003cp\u003eLike \u003ca href=\"https://polymer-library.polymer-project.org/\"\u003ePolymer\u003c/a\u003e before, \u003ca href=\"https://lit.dev/\"\u003eLit-Element\u003c/a\u003e uses javascript or typescript files for code, templates and styles by default, to enable the use of javascript variables. While this may be useful in some cases, personally I prefer having my style definitions in separate files mainly to benefit from \u003ca href=\"https://sass-lang.com/\"\u003eSass\u003c/a\u003e, but also the added benefit of editor assistance like highlighting and autocompletion. Fortunately this can be changed with a bundler like \u003ca href=\"https://webpack.js.org/\"\u003eWebpack\u003c/a\u003e.\u003c/p\u003e","title":"Loading Sass files with Lit"},{"content":"Imagine you are playing a game of TicTacToe against your friend. Obviously you want to find an ideal strategy to increase your chances of winning. How can you determine your next move?\nIntroduction Let us start by labeling the fields of our grid. Each game is then a series of numbers chosen alternately by you and your opponent. Instead of writing a list of all possible games that could be played, we draw them up as a tree:\nIn this case F 0 stands for marking the field 0. The children of a node now make up all possible next moves from this state of the game. Once the game ends the corresponding branch will stop as well and we have a leaf (labeled by v).\nThe value of those leaves is given by the final state of the game: -1 if your opponent wins, 1 if you win and 0 for a draw. Of course you want to pick your next move such that you may end up at a leave resulting in a 1, while your opponent will try the opposite. As such one player tries to maximize the root value, while the other tries to miminize it. So we are interested in the value of the current root of this tree.\nProblem Definition Game Tree A game tree is a rooted tree in which internal nodes at an even distance from the root are labeled MIN and internal nodes at odd distance are labeled MAX. Each leaf is associated with a real number, its value. The goal is to determine the value of the root node.\nAdditionally, we are interested in the number of leaves that need to be evaluated to compute this value, any other operations are ignored.\nFor ease of presentation I only consider full binary trees with values in $\\lbrace 0,1 \\rbrace$. Let such a tree be denoted as $T_{2,k}$, with $k$ layers of MAX nodes and $k$ layers of MIN nodes. Hence, the total height of the tree is $2k$ and it has $4^{k}$ leaves. As the values can be interpreted as boolean values, the two types of internal nodes can be regarded as AND respectively OR operations.\nDeterministic Algorithm A Game Tree can be evaluated by recursively calculating the values of its child nodes. At each step the algorithm has to decide which child to regard first. This choice has to be deterministic for a deterministic algorithm. Short-circuiting may be used to skip the evaluation of the second child node if the first already returned 0 for a MIN node or 1 for a MAX node respectively. But for any deterministic choice for the order of evaluation there exists a worst case such that the algorithm needs to evaluate all $d^{2k}$ leaves. Thus, its worst case number of steps is linear in the number of leaves.\nRandomized Algorithm The randomized algorithm works almost the same as the deterministic one. But instead of a deterministic order for the evaluation of its children, the algorithm chooses each child node first with equal probability. The expected number of leaves that have to be evaluated can then be reduced to $3^{k}$, which is roughly $n^{0.792}$ with $n$ as the number of leaves.\nProof The claimed property is proved by induction over $k$. First note that due to short-circuiting a MIN node evaluating to 0 and a MAX node evaluating to 1 are the same case, with the values flipped. The same is true for a MIN node evaluating to 1 and a MAX node evaluating to 0.\nFirst consider the two cases for $k=1$.\nMIN root 0, k=1 If a MIN root evaluates to 0, at least one of its child MAX nodes must evaluate to 0. With probability $\\frac{1}{2}$ this node is selected first. In turn both its children must evaluate to 0 as well. Thus, picking the correct node results in $2$ leaves being evaluated ($\\red{\\text{red part}}$).\n\u003c!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\"\u003e The other node is picked with probability $\\frac{1}{2}$ as well. As it evaluates to 1, it must have at least on child with value 1. This child is again picked with probability $\\frac{1}{2}$ ($\\blue{\\text{blue part}}$). In that case the blue and red nodes have to be considered for a total of 3.\nWith probability $\\frac{1}{2}$ the wrong node is selected first again ($\\green{\\text{green part}}$), which results in all 4 leaves being evaluated.\nThe expected number of leaves that have to be considered is thus: $$ \\begin{align*} \\red{\\frac{1}{2} \\cdot 2} + \\blue{\\frac{1}{2} \\cdot \\frac{1}{2} \\cdot 3} + \\green{\\frac{1}{2} \\cdot \\frac{1}{2} \\cdot 4} = \\red{1} + \\blue{\\frac{3}{4}} + \\green{1} \\leq 3^{1} \\end{align*} $$ MIN root 1, k=1 On the flip side, if a MIN node evaluates to 1 both its children must be considered. But the child nodes are MAX nodes and must have at least one child node with value 1 again. With probability $\\frac{1}{2}$ this node is chosen first in each case.\n\u003c!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\"\u003e Once again there is also a $\\frac{1}{2}$ chance for each MAX node to select the wrong leaf first, in which case both its leaves must be evaluated.\nThis results in an expected number of leaves to be considered:\n$$ \\begin{align*} 2 \\cdot \\left( \\red{\\frac{1}{2} \\cdot 1} + \\blue{\\frac{1}{2} \\cdot 2} \\right) = \\red{1} + \\blue{2} \\leq 3^{1} \\end{align*} $$ As a tree with a MAX root works the same as a MIN node with all values flipped, it holds that the expected number of leaves to be considered $\\mathbb{E}(T_{2,k}) \\leq 3^{k}$ for $k=1$.\nMIN root 0, $k \u003e 1$ We now assume that our statement $\\mathbb{E}(T_{2,k-1}) \\leq 3^{k-1}$ holds for $k-1$. This case can be thought of and is proved equivalent to the $k=1$ case but each leaf is now another Game Tree $T_{2,k-1}$ instead. Hence, evaluating a leaf instead evaluates $\\leq 3^{k-1}$ actual leaves.\nHence, the number of leaves evaluated is given by:\n$$ \\begin{align*} \\left( \\red{\\frac{1}{2} \\cdot 2} + \\blue{\\frac{1}{2} \\cdot \\frac{1}{2} \\cdot 3} + \\green{\\frac{1}{2} \\cdot \\frac{1}{2} \\cdot 4} \\right) \\cdot 3^{k-1} \\\\ = \\left( \\red{1} + \\blue{\\frac{3}{4}} + \\green{1} \\right) \\cdot 3^{k-1} \\leq 3 \\cdot 3^{k-1} = 3^{k} \\end{align*} $$ MIN root 1, $k \u003e 1$ The same argument for the equivalence of this case to the case MIN node 1, $k = 1$ holds here as well, with the leaves replaced by smaller Game Trees $T_{2,k-1}$ and the expected number of leaves to be evaluated is then:\n$$ \\begin{align*} 2 \\cdot \\left( \\red{\\frac{1}{2} \\cdot 1} + \\blue{\\frac{1}{2} \\cdot 2} \\right) \\cdot 3^{k-1} = \\left( \\red{1} + \\blue{2} \\right) \\cdot 3^{k-1} \\leq 3^{k} \\end{align*} $$ As previously mentioned, the cases for a MAX root can be proved analogously as the MIN cases.\nConclusion This concludes that in all cases the expected number of leaves that the randomized algorithm has to evaluate is less than or equal to $3^{k}$. Of course the worst case still has to evaluate all leaves.\nUsing a randomized algorithm it is, thus, possible to achieve an expected number of steps, which is strictly better than the deterministic approach.\nFor games with a lot of decisions like chess, the randomized algorithm is still much to slow to process the whole tree. In such cases a partial tree that only evaluates to a certain depth can be used. The values of the leaves must then be determined by the state at that time. For example giving each chess piece you hold a value depending on its position and subtracting the score of your opponent.\nReferences Motwani R. \u0026amp; Raghavan P. (1995). Randomized Algorithms\n","permalink":"https://ateon.ch/posts/game_tree_evaluation/","summary":"\u003cp\u003eImagine you are playing a game of TicTacToe against your friend. Obviously you want to find an ideal strategy to increase your chances of winning.\nHow can you determine your next move?\u003c/p\u003e","title":"Randomized Algorithms: Game Tree Evaluation"},{"content":"As those following the news about the Polyring may have read on xyquadrat, our widget can now be styled with themes. For those interested about the inner workings I will provide some technical information here.\nThe component makes heavy use of css variables alongside the attribute theme, which can be set on the component. Let\u0026rsquo;s walk through the needed setup, which consists of both javascript code and css descriptions.\nJavascript setup Fortunately webcomponents already have the functionally to observe attributes. We can simply declare an attribute as observable using a built-in funciton. This allows for hot-switching instead of only loading the attribute once in the beginning.\nstatic get observedAttributes() { return [\u0026#39;theme\u0026#39;]; } Each time the value changes the triggered event can be observed with yet another built-in method. It\u0026rsquo;s usually a good idea to check for the validity of this newVal and if it actually corresponds to our attribute. This is increasingly important if we observe more than just one attribute.\nattributeChangedCallback(attrName, oldVal, newVal) { if(attrName == \u0026#34;theme\u0026#34; \u0026amp;\u0026amp; newVal \u0026amp;\u0026amp; oldVal !== newVal) { // act on new theme } } Using a lookup table we can then handle predefined themes, which makes it easier to embed. If the given value is not found in the table, we assume that it must be an url to an external file.\nthemes = { default: \u0026#34;assets/themes/default.json\u0026#34;, dark: \u0026#34;assets/themes/dark.json\u0026#34; } var url = this.themes[newVal] ?? newVal; The corresponding internal or external file is then loaded, parsed as json and each css property is updated. You can find an example for such theme file on xyquadrat.\nfetch(url).then(response =\u0026gt; response.json()) .then(val =\u0026gt; { for(var item in val) { this.style.setProperty(item, val[item]); } }).catch( val =\u0026gt; { console.error(val); }); CSS setup A css variable or css custom property can be used with the var function in css. For example for the webring-banner:\n.webring-banner { background-color: var(--background-color, #FFF); border: 1px solid var(--outer-border-color, #DDD); } Take note that varallows passing a default value in the format: var(--my-variable, default_value), but it is not necessary. Basically every kind of css parameter can be used in a variable, so you could have dynamic borders or even hide an element in one theme. These variables can also be stacked to allow for both broad and specific control:\n.webring-banner__info { border: 2px solid var(--inner-border-color, var(--outer-border-color, #DDD)); } Additionally if you\u0026rsquo;re using scss and aren\u0026rsquo;t keen on repeating the same var function for each component that uses these properties, you can integrate them with scss variables:\n$text-color : var(--core-text-color, black); .text { color: $text-color; } Unfortunately these css custom properties can not be used in scss functions like scale-color. As those are parsed at build time, but the value from a css variables is only present at run time. However css variables can be used in css functions like calc.\nConclusion Css custom properties allow for relatively easy theme support both to your website as well as webcomponents. Many css frameworks like Materialize or Bootstrap make heavy use of css variables to style elements dynamically.\n","permalink":"https://ateon.ch/posts/some-technical-information-about-the-polyring-widgett/","summary":"\u003cp\u003eAs those following the news about the Polyring may have read on \u003ca href=\"https://xyquadrat.ch/2021/04/24/polyring-widget-theming.html\"\u003exyquadrat\u003c/a\u003e, our widget can now be styled with themes. For those interested about the inner workings I will provide some technical information here.\u003c/p\u003e\n\u003cp\u003eThe component makes heavy use of \u003ca href=\"https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties\"\u003ecss variables\u003c/a\u003e alongside the attribute \u003ccode\u003etheme\u003c/code\u003e, which can be set on the component. Let\u0026rsquo;s walk through the needed setup, which consists of both javascript code and css descriptions.\u003c/p\u003e","title":"Some technical information about the Polyring widget"},{"content":"There are multiple ways to send a secret message. The best known and usually used, especially over the internet, is by encrypting the message and later decrypting it. But it\u0026rsquo;s not the only possibilty. Steganography is the process of hiding information within another carrier medium, fooling everyone else into thinking that the carrier is the only message.\nThe basic principle ist simple:\nAdd a start and end tag to our message. Convert the message to a byte string using UTF8. Overwriting the least significant bits in each pixel with our byte string until the whole message is stored. Let\u0026rsquo;s look at the last step if we want to use the two least significant bits: As an example we want to store 10 in a channel that currently stores 10100111. First we apply a bit mask with the \u0026lsquo;and\u0026rsquo; operation, then we apply our data with an \u0026lsquo;or\u0026rsquo; operation. This pixel data is then written back into the image.\n10100111 \u0026amp;\t11111100 |\t00000010 = 10100110 If only using the least siginificant bits, the difference can not be seen. Even detecting that the image has been changed is difficult if the original can not be used as a reference.\nHere are the source files if you want to try out the application.\nFurther reading: https://www.garykessler.net/library/steganography.html ","permalink":"https://ateon.ch/posts/using-steganography-to-send-messages-hidden-in-an-image/","summary":"\u003cp\u003eThere are multiple ways to send a secret message. The best known and usually used, especially over the internet, is by encrypting the message and later decrypting it.\nBut it\u0026rsquo;s not the only possibilty. Steganography is the process of hiding information within another carrier medium, fooling everyone else into thinking that the carrier is the only message.\u003c/p\u003e\n\u003cp\u003eThe basic principle ist simple:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eAdd a start and end tag to our message.\u003c/li\u003e\n\u003cli\u003eConvert the message to a byte string using UTF8.\u003c/li\u003e\n\u003cli\u003eOverwriting the least significant bits in each pixel with our byte string until the whole message is stored.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eLet\u0026rsquo;s look at the last step if we want to use the two least significant bits:\nAs an example we want to store \u003ccode\u003e10\u003c/code\u003e in a channel that currently stores \u003ccode\u003e10100111\u003c/code\u003e. First we apply a bit mask with the \u0026lsquo;and\u0026rsquo; operation, then we apply our data with an \u0026lsquo;or\u0026rsquo; operation. This pixel data is then written back into the image.\u003c/p\u003e","title":"Using steganography to send messages hidden in an image"},{"content":"I\u0026rsquo;m a software engineer based in Switzerland. I recently completed my Master\u0026rsquo;s in Computer Science at ETH Zurich, where my thesis focused on formally verified ASN.1 encoders and decoders — generating Python code from protocol specifications and proving its correctness using Nagini.\nAlongside my studies I co-developed Vulcard, a cooperative deck-building game released on Steam. Built with MonoGame in C# as part of ETH\u0026rsquo;s Game Programming Lab, it features non-turn-based combat where two players fight enemies of increasing difficulty together.\nMember of the Polyring webring\n","permalink":"https://ateon.ch/about/","summary":"\u003cp\u003eI\u0026rsquo;m a software engineer based in Switzerland. I recently completed my Master\u0026rsquo;s in Computer Science at ETH Zurich, where my thesis focused on formally verified ASN.1 encoders and decoders — generating Python code from protocol specifications and proving its correctness using \u003ca href=\"https://github.com/marcoeilers/nagini\"\u003eNagini\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eAlongside my studies I co-developed \u003ca href=\"https://store.steampowered.com/app/3764530/Vulcard/\"\u003eVulcard\u003c/a\u003e, a cooperative deck-building game released on Steam. Built with MonoGame in C# as part of ETH\u0026rsquo;s Game Programming Lab, it features non-turn-based combat where two players fight enemies of increasing difficulty together.\u003c/p\u003e","title":""}]