(If you haven’t already, check out my Day -1 post for context on this project!)
LangJam GameJam 2025: Day 1
Things went okay today! I learned a lot about rendering — specifically, combining pipelines in an efficient way — but I had much less time to work on it today.
This blog post will be briefer because I am very sleepy.
Here is the aforementioned castle: Schloss Lichtenstein.
Beautiful.
Poor connection.
Milestones
Rendering SVGs (c11fc8b)

Originally, I had ambitiously planned to use Blitz, an HTML renderer, to render arbtirary HTML/CSS content into my grid cells.
However, after letting an AI wrestle with this task for long enough, I realized that this was perhaps overambitious: Blitz is really designed to render an entire webpage, not tiny snippets of HTML inside a larger rendering context.
Instead, I decided to use usvg, resvg and tiny-skia to render SVGs into a buffer I can share with the shader. I’m also using a basic pixel cache to make performance tolerable.
At this point, I officially retired by tiny hexadecimal digit renderer, instead opting to use SVGs to render cell values.
Editing cells (5de8ae0)

Next, I made things interactive in a very basic way: I can now modify the contents of a single selected cell.
This is currently implemented extremely naively, but it’s enough to play with things in real-time. Thinking through this, I fear I may need to add yet another dependency to handle text input properly — and I may need to look beyond the Bevy Ecosystem for that. Right now, I’m most seriously considering bevy_egui, at the risk of my rendering pipeline becoming comically layered: (egui -> bevy -> resvg -> tiny-skia -> wgsl).
At this stage, I was just rendering the computed value of each cell, so there was no way to look at a formula without selecting a cell. This inspired me to implement lenses.
Lenses (5de8ae0)

Lenses are a very simple way of selecting what parts of a cell should be rendered.
fn generate_svg(cell: &crate::cell::Cell, col: i32, row: i32, lens_state: &LensState) -> String {
let mut elements = String::new();
// 1. Base Content (Value or Rich)
let is_rich = (col == 0 && row == 2) || (col == 1 && row == 2);
if is_rich && lens_state.show_value {
// Use custom SVG body for rich cells
if col == 0 && row == 2 {
elements.push_str(r##"<rect width="80" height="30" fill="#e0f7fa"/><text x="5" y="20" font-family="sans-serif" font-size="12" fill="#006064">Status: OK</text>"##);
} else if col == 1 && row == 2 {
elements.push_str(r##"<circle cx="15" cy="15" r="8" fill="#4caf50"/><text x="30" y="20" font-family="sans-serif" font-size="12" fill="#333">Active</text>"##);
}
} else if lens_state.show_value {
// Default text rendering
let text = match &cell.value {
evalexpr::Value::Int(i) => i.to_string(),
evalexpr::Value::Float(f) => format!("{:.2}", f),
evalexpr::Value::String(s) => s.clone(),
evalexpr::Value::Boolean(b) => b.to_string(),
evalexpr::Value::Empty => "".to_string(),
evalexpr::Value::Tuple(_) => "Tuple".to_string(),
};
elements.push_str(&format!(r##"<text x="40" y="20" font-family="sans-serif" font-size="14" fill="black" text-anchor="middle">{}</text>"##, text));
}
// 2. Position Lens
if lens_state.show_position {
let coord_text = crate::formula::coord_to_name(col, row);
// Top-left, small light gray
elements.push_str(&format!(r##"<text x="2" y="8" font-family="sans-serif" font-size="8" fill="#aaaaaa">{}</text>"##, coord_text));
}
// 3. Formula Lens
if lens_state.show_formula && cell.is_formula {
// Bottom, small blue
let formula = cell.raw.replace("<", "<").replace(">", ">").replace("&", "&");
elements.push_str(&format!(r##"<text x="2" y="28" font-family="sans-serif" font-size="8" fill="blue">{}</text>"##, formula));
}
format!(r##"<svg xmlns="http://www.w3.org/2000/svg" width="80" height="30">{}</svg>"##, elements)
}
I want to make this more dynamic soon, as I can imagine some really cool interactions with a system that changes how it is being rendered during runtime, but these static lenses will be good for now.
A simple model could be something like this:
{
value: 10,
render: (v) => `<text>${v}</text>`,
}
But I want the language to be ergonomic… Maybe a good use for the lenses editing a dimension instead of using a dictionary?
Small, notable fixes
- Moved to a sparse representation of the grid
- Added colors, tracking lines to the axis
- Made grid toggleable
What’s in store for tomorrow
I’m being heckled by some rabble to actually build the fun parts of a language.
(A message from an anonymous reader)
I feel like I’ve been using a little too much AI so far, and like many of my colleagues, am beginning to fear the reliance I have on GitHub Copilot.
Tomorrow, I am challenging myself to channel my PL energy, and build a simple language by hand tomorrow, sans all AI assistance. I’m considering shedding the Excel coordinate system, and certainly want to add better support for dynamic references. I want to also think through ranges, and using them as a dictionary of context.
I know I keep saying that I want to build the actual game, but, this time I really mean it. I can at least get the Game of Life tomorrow… right?
(Spoiler: I only kinda did it on Day 2)