Agentic Software Engineering for Not-Dummies
Part 1: Moving from the Sandbox to the Driver’s Seat

For a developer whose career began in the mid-1990s, the current landscape of "AI-assisted coding" can feel incredibly loud, yet fundamentally hollow. Today, finding actual architectural value from AI tutorials is like looking for a needle in a haystack—except the haystack is growing exponentially every day, fueled by low-effort content. The internet is flooded with so called guides showing how to generate isolated code snippets, build generic web apps from scratch, or let an LLM vomit hundreds of lines of unverified code into a repository and that final live deployment.
That is not software engineering. That is syntax generation.
Let's be completely honest: This blog series will not give you a "TOP-100 Secret Prompts for Vibe Coding" or any similar monetized, clickbait bullshit. We are not here to sell you shortcuts. If you are reading this because you want to get rich by next week using AI, you are in the wrong place, and this blog is absolutely not for you.
As seasoned developers, our value doesn't lie in how fast we can type a semicolon; it lies in architectural planning, resource management, and structural integrity. When we build anything serious, like a custom 2D game engine from the ground up using C++ and SDL3, we care about cache-friendliness, memory allocations in hot per-frame loops, and avoiding technical debt.
But we also care deeply about design and code quality. True code quality always follows the quality of your design. We must care about architectural virtues like readability, usability, extendability, and maintainability—especially when building a shared library or a core engine layer. They are not just buzz-words in our bullshit bingo. Reality is when you release an API, you are tying the game developer to your decisions. And making modifications—like changing the signature for a frozen API already in use in the public is not easy. If your API is an unreadable, rigid mess, you are trapping them in architectural hell.
To prevent this, every core engine layer must be built upon these structural pillars:
Extendability: A great engine doesn't try to predict every future feature; it provides a clean, abstract framework where new systems can be plugged in without shattering the existing codebase. If adding an ambient lighting system requires rewriting your entire sprite batcher, your architecture has failed.
Maintainability: Code is read and modified far more often than it is written. High maintainability means isolating responsibilities cleanly so that debugging or refactoring a single module doesn't trigger a catastrophic domino effect across unrelated subsystems.
Readability & Usability: Your API is a user interface for programmers After you release the API, you can't just go on and change it as you wish. External parties rely on your API signature, if you change it, all the applications that depend on it fail—and the hell freezes over (been there, done that) . Besides of APIs, all code should be intuitive, self-documenting, and elegant. A game developer should be focusing on gameplay logic, not fighting obscure, poorly-named function signatures or hidden side-effects under the hood.
If your goal is merely to "vibe-code" a custom engine that beats Unreal Engine 5—or a part of it e.g. Better Lumen, which makes you rich overnight, you are going to end up with nothing but vibe-slop. It will inevitably become unreadable, unmaintainable, and utterly unusable due to leaking abstractions, rigid technical debt, and catastrophic architectural side-effects.
Big tech corporations constantly beat the drum, claiming that traditional programmers are becoming obsolete and that "99% of our code is now generated by AI." To that, I say: bullshit.
Just because an AI-driven tool is widely accessible to people who have never written a line of code in their lives does not mean the output is automatically production-grade, secure, or architecturally sound. On the contrary—if you attempt to manifest a complex SaaS platform with a single, glorious "Mega-Prompt," you are going to receive exactly one thing: a non-glorious, unmaintainable Mega-Slop artifact. I came up with this phrase when I was making a certain song in the past: "A tool is just a tool — and a tool by itself is never a fool, but the one wielding it can become one". By vibe-coding single-prompt slop, you automatically nominate yourself as an "Artifact Engineer." Nice title in your box of resumes and certificates, but the hiring HR already knows what they're gonna get from that box—and even though the color looks like chocolate, it's something else entirely.
Real software engineers and software architects are still very much needed, more today than ever. Hopefully I can show you exactly why throughout this blog series.
This blog series is a practical diary of how to build a low-level game engine (thengine) and a procedural roguelike on top of it (emberborn) by treating an Artificial Intelligence not as a magic code generator, but as an autonomous junior engineer under strict architectural supervision.
While this series revolves tightly around game engines and game architecture, these exact same core principles apply universally to all software engineering disciplines. You cannot "vibe-code" a maintainable, extendable, and secure enterprise SaaS CRM system or a complex cloud infrastructure if you don't systematically plan your architecture and break your goals into isolated, manageable packages. The rule of disciplined engineering remains unbroken, no matter the domain.
But planning your architecture doesn't have to mean drowning in corporate process charts or reading endless manuals before writing your first line of code. With this article I intend to offer you my architectural guidance as a seasoned software engineer and architect, how to command AI agents effectively without drowning in a sea of bad code or burning through your token economy.
Just Start Under the Hood
If you have an idea—whether it's building a project from scratch or just mastering agent-assisted software development—don't overthink it, and don't obsess over future roadblocks. Just start building the project with the skills you already have right now. Many promising projects die before they have a single paper of design document or any code. The fear of making mistakes or failing are the biggest barriers that stop projects before they have a chance to blossom at all.
You do not need to read a mountain of textbooks or study library documentation for weeks before you are allowed to touch the editor. The architecture guide (like your CLAUDE.md canvas) is simply a safe perimeter that allows you to safely work one logical step at a time.
Commanding the Man-o'-War
To understand how to command an AI agent effectively without drowning in a sea of bad code or burning through your token economy, you need to change your perspective on who is running the show.
Remember: You are the architect, and you are in control. The agents will do exactly what you ask—often far too literally.
Imagine a massive, double-decked Man-o'-War warship crashing through stormy seas toward the next great battle. The ship has a relentless crew performing the backbreaking tasks, but you are the Captain holding the helm, guiding the vessel on its true course.
You are the Architect: You possess the map that dictates where the ship must go, and you define exactly how the operations should run.
The Crew is the Agents: A specialized group of autonomous AI workers, executing their assigned duties when commanded. Them being autonomous agents doesn't mean you shouldn't listen to what they say, or ask guidance from them either. Just the opposite—listen to what your agent tells you. An experienced crewman might know things you don't and be even willing to help you. Same goes for AI agents.
The Ship is your Constitution: The vessel itself is the physical structure that allows you to turn the helm—your explicit architectural instructions on how everything must be done and to what quality. The ship you are steering is your
CLAUDE.mdorARCHITECTURE.mddocument.The Destination: Your client awaits at the ship's final port.
The Turbulent Ocean: This brings the unexpected engineering challenges, shifting requirements, and brutal bugs that you and your ship must overcome.
If the ship sinks, the captain sinks with it into the dark depths. Do not let your project capsize. Don't let your ship sink at the very first hurdle—because those storms will hit you far more often than I dare to admit here.
Why You Must Build Piece by Piece
The only way to keep your Man-o'-War afloat is to break your grand vision down into small, isolated, and logically independent functional pieces—and conquer them one by one:
Your Architecture Stays Sane: By forcing yourself to focus on just one independent block at a time, you naturally prevent the code from tangling into a monolithic nightmare. You keep complete architectural control.
Small Pieces Are Easily Verifiable: Instead of trying to build a massive system all at once, you implement small, logical features. Every single piece you build must bring something concrete to the canvas that you can immediately run, test, and verify.
Massive Token and Cost Savings: AI agents thrive on isolation. When your tasks are laser-focused, your prompts stay short and clear. The agent uses vastly fewer resources (tokens) when you ask it to build a specific mathematical vector utility instead of blindly commanding it to "Write me a dynamic global illumination lighting system."
Learning Under Fire & Asking for Directions (Eliminating Waste): Whenever you hit a concrete wall or feel uncertain about the next step, that is when you pause and study the specific theory required to solve it. Do not study everything in advance; pre-emptive cramming usually turns into technical waste. Learn exactly what you need, exactly when you need it, and apply it directly to the engine.
And remember: you can—and should—ask your agent for traveling instructions. If you don't know how to split a massive feature into smaller logical pieces, use the agent as your senior consultant. Instead of blindly commanding it to build the whole system, ask for a roadmap: "How would you suggest I start implementing point lights into this game engine, given that we currently only support basic unlit texture rendering?" Let the experienced crewman draw the sub-steps for you, then execute them one by one.
Step 1 in Action: Bootstrapping the Empty Canvas
To put this methodology into perspective, let's look at how the foundations of thengine were actually laid.
As the chief architect, I didn't just fire up an agent and type "Write me a game framework." That is where most developers shoot themselves in the foot. Instead, I grabbed the wheel and handled the first four foundational commits completely by hand, without a single line of agent-generated code. I manually established the physical directory structure, hand-crafted the initial core infrastructure files, and wrote the base loop in main.cpp.
However, it is worth noting that you don't strictly have to build the initial directories and files yourself to follow this methodology. You can absolutely start from a completely fresh, empty directory and let the agent bootstrap the initial setup.
But if you choose that path, the core architectural principle is that the CLAUDE.md (or ARCHITECTURE.md) is the constitution (or your framework guide) and should be firmly established and written before any actual application code is generated. You don't even have to type it out yourself; you can use the agent as an interactive whiteboard. You can prompt it right out of the gate: "Let's co-create the guidelines and conventions for this project and document them in a CLAUDE.md file using Markdown syntax." You dictate your design principles, and let the agent write the blueprint.
Crucially, establishing this system guide early is the ultimate hack for your token economy. By keeping your architectural rules, naming conventions, directory paths, and tech stack parameters stored permanently in a single file, you don't have to repeat them in every single prompt. The agent reads it from the context of the repository, keeping your prompt payload lightweight, highly focused, and incredibly cost-effective.
Before a single line of C++ or CMake is written, that blueprint should explicitly solidify:
The Target Directory Tree: How the workspace, projects, and libraries are isolated.
Coding Style & Conventions: Naming rules, syntax boundaries, and layout paradigms, API design and even how to comment the code, if so desired.
The Tech Stack Requirements: The exact programming language version (e.g., C++20 or C++23), compiler configurations, and memory allocation constraints.
In my case, during those initial manual commits, I chose to inject CLAUDE.md into the root of the repository by hand as our framework's constitution. Later I will show you my CLAUDE.md but you can find it in thengine GitHub repository,
(Note: I named this file CLAUDE.md because I was setting up the environment with Anthropic's Claude Code tool in mind at first but then I chose Antigravity. However, this architectural pattern is entirely tool-agnostic. Depending on your setup and preferences, you can—and should—name this document whatever fits your ecosystem best, whether it is ARCHITECTURE.md, ENGINE_RULES.md, or a custom system guide.)
Before an agent was even allowed to see the project, this constitution laid down the law: it defined our system boundaries, memory rules, naming conventions, and strict C++/SDL3 style guidelines. I built the cage before buying the tiger.
Always remember that you can—and absolutely should—iteratively update this configuration file throughout your project's timeline as the engine evolves and the agent requires more specific constraints. By injecting a file like CLAUDE.md, you are essentially providing your AI agent with a persistent, local "System Prompt" that automatically wraps around every single task you assign to it, destroying guesswork at the source.
Enter the Agent
Once the structural scaffolding and CLAUDE.md were established, it was time to bring in the agent as a mechanical multiplier. The very first autonomous interaction occurred in commit 86d5ca20cb1743536a0074238f719c2a0ca592a7.
Armed with my manual layout and the strict constraints defined inside CLAUDE.md, the agent was fed a highly constrained objective: implement the core lifecycle loop using SDL3 while adhering to our strict performance guidelines (like avoiding per-frame heap allocations).
Here is the exact repository footprint of that first successful agent task:
commit 86d5ca20cb1743536a0074238f719c2a0ca592a7
Author: Erno Pakarinen <erpakari@gmail.com>
Date: Thu Apr 30 07:21:55 2026 +0300
Implement core Game base class with SDL3 integration
Added engine/include/thengine/Game.h and engine/src/core/Game.cpp. Implemented the main run loop including SDL3 subsystem initialization, window creation, frame timing (delta time, fps limits), event polling, and lifecycle hooks (onInitialize, onUpdate, etc). Uses modern C++17 RAII for SDL object lifecycle management.
CLAUDE.md | 13 +++++
engine/doc/Architecture.md | 10 ++--
engine/include/thengine/Game.h | 58 ++++++++++++++++++++++
engine/src/core/Game.cpp | 110 +++++++++++++++++++++++++++++++++++++++++
4 files changed, 186 insertions(+), 5 deletions(-)
The Autonomous Result
Because the agent was strictly bound by the pre-existing architecture and the local system prompt constraints of CLAUDE.md, it couldn't wander off to write bloated "vibe-slop". If you look closely at the stat log: it simply appended its own local state notes to CLAUDE.md and then generated 168 pristine lines of implementation and header logic.
It executed the boilerplate configurations flawlessly, integrated the engine's initialization loop with SDL3, ran the compiler, and verified that a pristine, empty canvas opened up on the screen.
It's a Living Document, Not the Ten Commandments
Always remember that a file like CLAUDE.md is an evolving contract, not a rigid set of rules written in stone. You can—and absolutely should—iteratively update it as your project's timeline progresses and engineering requirements change.
And depending on the Agent you are using (Claude Code, Antigravity, Cursor...), it is highly recommended to state: "I updated the CLAUDE.md document, please follow the new fundamental rules" in your very next prompt. This simple habit ensures the LLM flushes its structural assumptions and parses your updated boundaries immediately.
To prove this point with a real-world example from thengine's development: look at that initial agent commit above. At the start of the project, our target language standard was strictly C++17. However, as the rendering pipeline grew, we realized we needed zero-allocation string slicing and modern formatting utilities.
Instead of fighting the agent or trying to remember to type "use C++20" in every new prompt, I simply updated CLAUDE.md to enforce the C++20 standard. A few commits later (in b714426), the agent seamlessly migrated the entire text rendering pipeline to std::string_view and UTF-8 Unicode, completely eliminating old buffer allocation debt—all because the local "system prompt" had evolved alongside the project.
(In the upcoming posts, we will dig through our actual Git commit with more detail to show how this partnership evolved—culminating in an advanced 2D lighting shader engine and a zero-allocation Camera Dirty Flag optimization matrix.)
Pro-Tips for Masterful Agent Collaboration
To keep your crew operating at peak efficiency and prevent your code from drifting into chaos, embed these three tactical prompt habits into your daily workflow:
1. Split Large Tasks (The Divider): Never hand the agent a massive, multi-layered objective. If you ask for a full feature all at once, the agent has to guess the architecture, which leads to bloated code and broken boundaries. Break the work down to the absolute minimum functional unit (e.g., "Create the abstract interface for the input handler" instead of "Implement the entire gamepad and keyboard mapping system").
2. Be Explicitly Specific (The Commander): Eliminate all ambiguity. Specify your constraints, structural boundaries, and files to be touched before the agent writes a single line. Tell it exactly where data should flow, what standard to use, and what to avoid (e.g., "Implement this utility using C++20
std::string_viewand ensure no heap allocations occur in the hot path").3. Ask Before Implementing (The Strategist): If you are unsure about the best architectural approach, stop and ask for design possibilities first. Use the agent as a senior engineering consultant before writing code. A prompt like: "I want to implement view frustum culling. What are the top 3 implementation strategies for a 2D grid setup, and what are their performance trade-offs?" allows you to choose the best blueprint together before the crew touches the iron.
And I can't emphasize this enough, keep your CLAUDE.md up to date, make changes when you deem necessary. It's your blueprint.
A Real-World Master Prompt: The Camera Dirty Flag
To see how these principles string together, let’s look at a real-world prompt used during the development of thengine.
The goal was to implement a 2D camera system. Instead of lazily prompting "Write a 2D camera with zoom and rotation," which would result in a bloated class recalculating matrices on every single frame, the prompt was split, highly specific, and tightly constrained to performance:
The Prompt: "Let's implement the
Camera2Dclass for our engine. Follow our C++20 and SDL3 guidelines insideCLAUDE.md.Here are the specific constraints:
Use the Dirty Flag pattern: the view/projection matrix must only be recalculated when the camera's position, zoom, or rotation actually changes. Do not recalculate matrices per frame in the update hot path if the flag is clean.
Provide clean getters and setters for position (
glm::vec2), zoom (float), and rotation (float), which internally flip the dirty flag.Provide a
getTransformationMatrix()method that returns the cached matrix, recalculating it lazily only if the dirty flag is set.Before writing the full implementation, please pitch the structural outline of the header (
Camera2D.h) and verify it aligns with our memory allocation rules."
The Multiplier Effect: Tangible Testing
What makes this specific agent interaction a masterclass in development wasn't just the pristine Camera2D implementation. Because the task was constrained and decoupled, the agent had the resource room to go a step further.
Along with the core matrix logic, the agent autonomously spun up a sandbox test application layer. It wired the new camera directly into our engine's loop, mapped keyboard inputs to the camera controls, and provided a live canvas where I could visually drive the camera around, adjust zoom, and verify in real-time that the dirty flag optimizations were holding up under pressure.
But here is my absolute "WOW" moment from the entire experiment: the agent didn't just write the code and hope for the best—Antigravity actively executed the sandbox test application itself, capturing live screenshots during the runtime execution to visually analyze and deduce whether the rendering and matrix flag states behaved exactly as intended. Seeing the agent run the loop, look at the engine's output, and verify its own architectural work dynamically was the moment I realized the era of the autonomous junior developer had truly arrived.
Why this prompt works flawlessly:
It enforces isolation: It focuses strictly on one mathematical component (
Camera2D), not the entire rendering pipeline.It eliminates "Vibe-Slop": By explicitly demanding the Dirty Flag pattern and lazy evaluation, it strips away the agent's ability to write naive, unoptimized loop logic.
It delivers a concrete increment: By delivering both the feature and the input-mapped sandbox application, it gave me an immediate, working result that I could test, audit, and play with—exactly like a high-velocity Scrum sprint increment.
The Million-Dollar Token Trick: Start Light
Before we close this first chapter, here is an immediate, high-value tactical tip for your token economy: always default to a lighter, faster, and cheaper model first.
Many developers waste a fortune burning through top-tier premium models for standard implementation tasks. The truth is, if your architectural boundaries are properly set inside CLAUDE.md, you don't need a heavy-lifting behemoth to write pure functional logic.
To prove this point: practically every single prompt, feature iteration, and codebase expansion inside thengine has been executed using Google Gemini Flash.
By choosing a lightning-fast "medium-tier" model, I slashed my API costs to a fraction of a cent per prompt. Because the local system constraints inside the canvas were crystal clear, the lighter model had a flawless roadmap to follow. Save your premium model credits for complex, high-level architectural brainstorming sessions—let the lighter crewmen handle the daily ironwork.
Coming Up Next...
This is only the beginning of our voyage. In the upcoming parts of this series, we will pull back the curtain completely and analyze the raw, unedited reality of co-developing with AI agents. We’ll look at the good, the bad, and the downright ugly.
Here is what we will dissect next:
The Anatomy of my personal
CLAUDE.md: A line-by-line breakdown of the exact system rules, directory definitions, and quality standards that kept the agent bound to my vision.Deep-Dive Git History: We will go through the actual commit logs to show how the codebase evolved dynamically from basic unlit textures to an advanced 2D lighting shader engine.
Advanced Token Optimization: Hardcore strategies on how to structure your repository, strip context waste, and keep your agent interactions incredibly cheap and fast.
The helm is in your hands, the ship is built, and the crew is waiting. See you in Part 2.





