Three playable Java Swing games built on top of a custom-designed entity-command framework (the Kavunn Engine), backed by a full rendering pipeline, a 2D physics engine, an asset management system, and cross-platform surface abstractions:
- Snake — the classic grid-chase game.
- Blockfall — a harder falling-block puzzle inspired by classic line-clearing games, with gravity, rotation, speed scaling, and stack pressure.
- Ashwake — a top-down arena-survival roguelite with room progression, six enemy archetypes, projectile combat, modifier upgrades, and a built-in benchmark suite.
- Why This Is Interesting
- Contributing & Collaboration
- Overview
- Architecture
- Core Framework (
core/) - Rendering Pipeline (
render/) - Physics Engine (
physics/) - Asset Manager (
assetmanager/) - Platform Abstraction (
platform/) - Shared Wiring
- Snake Game
- Blockfall Game
- Ashwake Game
- Requirements
- Build
- Run
- Controls
- Smoke Tests
- Benchmarks
- Output Directories
- Extending the Project
These are not throwaway game demos. The project is a practical case study in software architecture — every moving part in all three games is wired through a hand-built entity-command framework that applies four classic Gang-of-Four design patterns in a single, cohesive system:
- The entire game loop runs through the Command pattern. Every tick, the world itself is queued as a
Relationcommand and executed by theContext. Player inputs (direction changes, rotations, drops, dashes, fire commands) are queued through the exact same pipeline — there is no special-case code for "user actions" vs. "engine ticks". - Game objects are a Composite hierarchy.
SnakeWorldownsSnakeandFoodas child entities.TetrisWorldownsTetrisBoardandFallingPiece.AshwakeRunWorldownsAshwakePlayerandAshwakeRoomWorld, which in turn owns enemies, projectiles, hazards, and pickups. The framework doesn't know what a snake, a tetromino, or a projectile is — it just sees a tree ofEntitynodes that it can traverse, update, and execute uniformly. - Relationships are modeled as a graph. The
EntitiesGraphmaintains an undirected adjacency list (world ↔ children). Entity relationships are data, not hardcoded method calls — making the system extensible without touching existing classes. - Parameters are first-class. Every entity inherits a typed key-value store (
Params), so runtime attributes can be attached, queried, and modified without subclassing. Ashwake uses this extensively for render metadata (layerHint,materialKey,animationState,glowLevel, etc.).
The result is that all three games contain zero framework-specific boilerplate. SnakeWorld.execute() just calls step(). TetrisWorld.execute() just calls tick(). AshwakeRunWorld.execute() just calls update(delta). The framework handles queuing, execution ordering, history tracking, and entity registration — while the game layers stay clean and focused on game logic.
The proof of this separation is Blockfall (built on core/ with no framework changes) and Ashwake (built on core/ + render/ with no core changes, adding the rendering pipeline and a full roguelite on top). The core/ package could be lifted out and reused for an entirely different domain — a simulation, an ECS prototype, a turn-based strategy — without changing a line.
Beyond the games, the project includes a multi-backend rendering pipeline (Java2D, OpenGL, OpenGL ES, Metal), a 2D physics engine with collision detection and rigid-body simulation, an asset management system with image-to-mesh preparation, and cross-platform surface abstractions for iOS and mobile — all built on the same entity-command foundation.
I'm actively looking for experienced developers, designers, and architects who want to collaborate on game projects — from small prototypes to production-grade systems.
If you're someone who cares about clean architecture, well-structured codebases, and building things the right way rather than the fast way, I'd like to hear from you. Whether you bring expertise in game engine internals, rendering pipelines, AI/pathfinding, multiplayer networking, or UI/UX design — there's room at the table.
We’re particularly looking for a small number of strong collaborators — especially UI/UX and visual designers alongside strong mid level game developers/system programmers — who care about clean systems, distinctive game feel, and thoughtful product direction.
- Engineers with strong fundamentals in OOP, design patterns, and systems thinking.
- Game developers experienced with Java, C#, C++, or similar — framework and engine-level work, not just scripting.
- Game designers who can craft compelling mechanics, progression systems, and player experiences from the ground up.
- World designers with a vision for level layout, environmental storytelling, and spatial flow.
- Graphic designers skilled in UI/HUD design, visual identity, iconography, and promotional assets.
- Technical artists who understand the intersection of aesthetics and performance.
- Architects interested in building reusable frameworks and tools that power multiple projects.
Yet, feel free to reach out even if you don't fit these categories exactly — if you're passionate about game development and want to contribute, I'd love to hear from you.
- A collaborative, no-ego environment where ideas are evaluated on merit.
- Codebases that prioritize clarity, modularity, and long-term maintainability.
- Serious project scope — this isn't about weekend throwaway demos. We're aiming to build something substantial and lasting.
If this resonates, reach out directly:
Please include a brief introduction, your area of expertise, and — if available — a link to your work (GitHub, portfolio, or a project you're proud of). Serious inquiries only.
The codebase started as a small reusable entity-command framework (the Kavunn Engine). It now includes three complete games, a rendering pipeline with multi-backend support, a 2D physics engine, an asset management system, and cross-platform surface abstractions.
| Layer | Package / Location | Purpose |
|---|---|---|
| Core Framework | core/ |
A generic, reusable entity-management engine with command queuing, graph-based relationships, composite hierarchies, and typed parameter storage. |
| Rendering Pipeline | render/ |
A multi-backend rendering pipeline architecture with Universe → Planet → Matter hierarchy, lifecycle management, and pluggable render tasks. Backends: Java2D, OpenGL (LWJGL), OpenGL ES (Android/iOS), Metal (iOS). |
| Physics Engine | physics/ |
A 2D physics simulation framework with rigid bodies, collision detection (narrow-phase + spatial-hash broadphase), geometry primitives, form authoring, raster-shape importing, and a pluggable pipeline mirroring the render architecture. |
| Asset Manager | assetmanager/ |
Asset cataloging, manifest loading/validation, image-to-mesh preparation, and OpenGL draft libraries for materials, textures, shaders, palettes, fonts, and UI modules. |
| Platform | platform/ |
Cross-platform surface and input abstractions for iOS (Metal + OpenGL ES) and generic mobile (OpenGL ES), including touch event handling and safe-area support. |
| Snake Game | Root — Snake*.java, Direction*.java, Food.java, RestartSnakeCommand.java |
The classic grid-chase game, modeled as entities and commands. |
| Blockfall Game | Root — Tetris*.java, FallingPiece.java, TetrominoType.java |
A falling-block puzzle game, built on the same core with no framework modifications. |
| Ashwake Game | ashwake/ |
A top-down arena-survival roguelite with room progression, modifier upgrades, six enemy archetypes, projectile combat, and a built-in benchmark suite. Uses the render/ pipeline. |
| Shared Wiring | Root — GameContextFactory.java, Main.java |
Entry point and shared context creation for Snake and Blockfall. |
All source files use Java package conventions (core, render, physics, assetmanager, platform.ios, platform.mobile, ashwake, or the default package for root files).
Playground/
├── core/ # Kavunn Engine — reusable entity-command framework
│ ├── Association.java # Identity + structural-update interface
│ ├── Context.java # Central command bus & entity registry
│ ├── Entity.java # Base entity: Params + Association + Relation + Composite
│ ├── EntitiesGraph.java # Adjacency-list graph of entity relationships
│ ├── Invoker.java # Batched command execution (broker pattern)
│ ├── ParamValue.java # Marker interface for parameter values
│ ├── Params.java # Key-value parameter store on every entity
│ ├── PrimaryTypeValue.java # Type-safe wrapper for int, double, String, boolean, generic
│ └── Relation.java # Command interface — void execute()
│
├── render/ # Rendering pipeline framework
│ ├── Universe.java # Top-level render context; mounts a Planet
│ ├── Planet.java # Abstract world entity with lifecycle + Matter list
│ ├── Matter.java # Render-param container extending EntitiesGraph
│ ├── RenderingPipeline.java # Abstract pipeline: validate → bind → iterate matters → dispatch tasks
│ ├── DefaultRenderingPipeline.java # Concrete pipeline with a pluggable task list
│ ├── RenderTask.java # Functional interface for render operations
│ ├── RenderBackend.java # Interface for backend drivers
│ ├── SwingRenderingPanel.java # JPanel adapter for Java2D rendering
│ ├── Java2DRenderBackend.java # Java2D backend
│ ├── Java2DRenderTask.java # Java2D-specific render task
│ ├── OpenGLRenderBackend.java # Desktop OpenGL backend
│ ├── LwjglOpenGLBridge.java # LWJGL 3.4.1 native bridge
│ ├── OpenGLESRenderBackend.java # Mobile OpenGL ES backend
│ ├── AndroidOpenGLESBridge.java # Android OpenGL ES bridge
│ ├── IosOpenGLESBridge.java # iOS OpenGL ES bridge
│ ├── MetalRenderBackend.java # Apple Metal backend
│ ├── IosMetalBridge.java # iOS Metal bridge
│ ├── NoOpRenderBackend.java # Silent backend for testing
│ └── ... # Constants, bridge exceptions, domain entities
│
├── physics/ # 2D physics engine
│ ├── scene/ # PhysicsScene, PhysicsSpace, PhysicsMatter
│ ├── body/ # PhysicsBody, PhysicsMaterial, MotionState
│ ├── geometry/ # Vector2, Transform2, Bounds2, Circle2, Quad2, Triangle2, Mesh2, Contour2, Triangulator2
│ ├── form/ # Form2 interface, CircleForm, QuadForm, TriangleForm, MeshForm, CompositeForm
│ ├── collision/ # CollisionDetector2, CollisionReport, ContactManifold, Raycast/Overlap/Sweep commands
│ ├── pipeline/ # PhysicsPipeline, DefaultPhysicsPipeline, PhysicsTask
│ ├── backend/ # DeterministicPhysicsBackend, SpatialHashBroadphase, NoOp
│ ├── authoring/ # AnchorPoint, FormComposer, FormMorpher
│ ├── importing/ # ContourTracer, RasterShapeExtractor — bitmap-to-physics-form conversion
│ └── palette/ # PhysicsColor, PaletteExtractor, PaletteSet, PaletteSwatch
│
├── assetmanager/ # Asset management system
│ ├── AssetManager.java # Central manager for entity bundles and draft libraries
│ ├── AssetManagerSmokeTest.java # Comprehensive smoke test
│ ├── catalog/ # AssetCatalog, AssetKey, EntityAssetBundle
│ ├── font/ # FontStyle
│ ├── manifest/ # Manifest loading, importing, validation, authoring adapter
│ ├── mesh/ # Image → mesh pipeline (EntityMeshSequence, ImageMeshBuilder)
│ ├── opengl/ # OpenGL draft library: materials, textures, shaders, palettes, fonts, UI
│ ├── pipeline/ # AssetPreparationPipeline, reports, tasks
│ ├── source/ # Image asset sources (BufferedImage, Path, EntityImageSequence)
│ └── ui/ # UiElementType
│
├── platform/ # Cross-platform surface abstractions
│ ├── ios/ # iOS: Metal + OpenGL ES surfaces, touch input, safe area
│ │ └── nativebridge/ # iOS native Metal bridge
│ └── mobile/ # Generic mobile: OpenGL ES surface, touch events
│
├── ashwake/ # Ashwake — arena-survival roguelite
│ ├── AshwakeLauncher.java # Entry point: launch, smoke test, benchmark
│ ├── AshwakeContextFactory.java # Context + entity graph wiring
│ ├── AshwakeRunWorld.java # Top-level run entity — room progression, combat, scoring
│ ├── AshwakePlayer.java # Player entity — health, ward, energy, dash, fire
│ ├── AshwakeRoomWorld.java # Room container — enemies, projectiles, hazards, pickups
│ ├── AshwakeEnemy.java # 6 enemy archetypes with distinct AI
│ ├── AshwakeProjectile.java # 5 projectile kinds with pierce/lifetime
│ ├── AshwakeHazard.java # Area-of-effect zones (friendly and hostile)
│ ├── AshwakePickup.java # 3 pickup types: health, ember, energy
│ ├── AshwakeModifier.java # 8 run modifiers (roguelite upgrades)
│ ├── AshwakeRoomKind.java # 5 room types: Combat, Reward, Event, Elite, Boss
│ ├── AshwakeCommands.java # 8 command classes (move, fire, dash, pause, etc.)
│ ├── AshwakeGamePanel.java # SwingRenderingPanel subclass with input handling
│ ├── AshwakeRenderFactory.java # Java2D rendering pipeline + visual styling
│ ├── AshwakeBenchmark.java # Performance benchmark suite (warmup + measured frames)
│ └── AshwakeBenchmarkScenario.java # 3 benchmark scenarios
│
├── GameContextFactory.java # Shared: creates Context + registers world & children
├── Main.java # Entry point: --smoke-test, snake, blockfall
│
├── Direction.java … SnakeWorld.java # Snake game (8 files)
├── TetrominoType.java … TetrisWorld.java # Blockfall game (8 files)
│
├── lib/lwjgl/3.4.1/ # LWJGL native OpenGL bindings
├── docs/ # Screenshots and documentation assets
├── out/ # Compiled output (Snake + Blockfall)
├── out_ashwake/ # Compiled output (Ashwake)
├── out_verify/ # Verification build output
├── LICENSE # Kavunn Engine Source-Available License
└── README.md # This file
The project applies four classic GoF patterns, all wired through the core framework:
| Pattern | Where | How |
|---|---|---|
| Command | Relation interface, DirectionCommand, RestartSnakeCommand, TetrisActionCommand, SnakeWorld.execute(), TetrisWorld.execute() |
Every user action and every game tick is encoded as a Relation object, queued via Context.queueCommand(), and executed in batch via Context.executeQueuedCommands(). |
| Composite | Entity.children |
Entities can contain child entities (e.g. SnakeWorld holds Snake + Food; TetrisWorld holds TetrisBoard + FallingPiece), enabling recursive operations across the hierarchy. |
| Mediator / Graph | EntitiesGraph |
An undirected adjacency-list graph tracks which entities are related. GameContextFactory registers world ↔ child edges automatically. |
| Invoker / Broker | Invoker |
EntitiesGraph extends Invoker to maintain a broker list of Relation objects. When relations (edges) are added to the graph, the corresponding entities are also registered as brokers for potential batch execution. |
All three games follow the identical pattern:
- A world class extends
Entity<Relation>(SnakeWorld/TetrisWorld) orPlanet(AshwakeRunWorld). - Child entities are attached to that world via
addChildEntity(). - A context factory (
GameContextFactory.create()orAshwakeContextFactory.create()) builds aContext, registers the world + all children in the entity list and graph, and adds world ↔ child edges. - Player actions are wrapped as
Relationcommands and queued through the context. - Each timer tick queues the world itself as a command, which runs the next step of game logic through
execute().
The game loop and the player input system are both routed through the same command pipeline — neither bypasses the framework.
The Kavunn Engine is fully generic — it knows nothing about snakes, tetrominoes, or games. Any domain can reuse it.
public interface Relation {
void execute();
}The Command abstraction. Every executable behavior — whether it is a game tick, a direction change, a block rotation, or a restart — implements this single-method interface. Relation objects are what get queued, executed, and tracked in command history.
public interface Association {
String getLabel();
void setLabel(String label);
int getId();
void setId(int id);
void update();
}Provides identity (id + label) and a structural update hook (update()). Both Entity and EntitiesGraph implement this interface, meaning individual entities and entire graphs share a common identity/update contract.
getLabel()/setLabel()— human-readable name.getId()/setId()— integer identifier.update()— hook for the Composite pattern; subclasses override this to propagate state changes down an entity tree.
public interface ParamValue { }public class PrimaryTypeValue<T> implements ParamValue {
// Wraps int, double, String, boolean, or any generic T
}A type-safe value container system. ParamValue is a marker interface. PrimaryTypeValue<T> provides factory methods:
| Factory Method | Wrapped Type |
|---|---|
PrimaryTypeValue.integer(int) |
Integer |
PrimaryTypeValue.doubleVal(double) |
Double |
PrimaryTypeValue.string(String) |
String |
PrimaryTypeValue.bool(boolean) |
Boolean |
PrimaryTypeValue.of(T) |
Any T |
These are stored inside Params objects attached to every Entity.
public class Params {
private Map<String, ParamValue> kwargs;
// constructor, add(), get(), static of() factories
}A key-value attribute store backed by a HashMap<String, ParamValue>. Every Entity extends Params (inheriting its map), so any entity can carry arbitrary named parameters at runtime.
Static convenience factories: Params.of("key", 42), Params.of("key", 3.14), Params.of("key", "text"), Params.of("key", true).
public class Entity<T extends Relation> extends Params implements Association, RelationThe central abstraction. Every game-world object (SnakeWorld, Snake, Food, TetrisWorld, TetrisBoard, FallingPiece) extends Entity. It unifies:
| Concern | Source |
|---|---|
| Attributes | Params (inherited) — key-value parameter map |
| Identity | Association (implemented) — id and label |
| Behavior | Relation (implemented) — execute() |
| Hierarchy | children: List<T> — Composite pattern for nested entities |
Key methods:
addChildEntity(T)/removeChildEntity(T)/getChildren()— manage composite children.execute()— default no-op; subclasses override to define per-tick behavior (e.g.SnakeWorld.execute()callsstep(),TetrisWorld.execute()callstick()).update()— default no-op; subclasses override for structural propagation.equals()/hashCode()— identity based on(id, label), enabling graph lookups via temporary proxy entities.toString()— debug representation:Entity{id=100, label='snake-world'}.
The constructor calls super(label, PrimaryTypeValue.integer(id)), storing the entity's id as its very first parameter in the inherited Params map.
public class Invoker {
private List<Relation> relationList;
void addBroker(Relation);
void executeBroker();
void removeBroker(Relation);
}A batch command executor (broker pattern). Maintains a list of Relation objects and can execute them all in sequence via executeBroker(). Used as the base class for EntitiesGraph.
public class EntitiesGraph extends Invoker implements AssociationAn undirected adjacency-list graph of entities. Each node is an Entity and edges represent relationships between entities.
Key methods:
| Method | Description |
|---|---|
addEntity(id, label) |
Insert a node into the graph. |
removeEntity(id, label) |
Remove a node and all edges pointing to it. |
addRelation(id1, label1, id2, label2) |
Create a bidirectional edge between two entities. Also registers both entities as brokers in the inherited Invoker. |
removeRelation(id1, label1, id2, label2) |
Remove a bidirectional edge and deregister brokers. |
getAdjacentVertices(id, label) |
Return all neighbors of a given entity. |
update() |
Iterate over every registered entity and call entity.update() (Composite propagation across the whole graph). |
The graph also implements Association, so it has its own group_id and group_label — allowing graphs themselves to be nested or referenced as identifiable objects.
public class Context {
private Relation state;
private List<Relation> entities;
private EntitiesGraph graph;
private Deque<Relation> commandQueue;
private Deque<Relation> commandHistory;
}The central mediator and command bus. It ties everything together:
| Field | Purpose |
|---|---|
state |
The "current" active relation (optional state reference). |
entities |
A flat list of all registered Relation objects (entities). |
graph |
The EntitiesGraph tracking entity relationships. |
commandQueue |
FIFO queue of commands waiting to be executed. |
commandHistory |
Stack of executed commands (supports potential undo functionality). |
Command lifecycle:
- Queue:
context.queueCommand(command)→ pushes ontocommandQueue. - Execute:
context.executeQueuedCommands()→ drains the queue, executing each command and pushing it ontocommandHistory. - Immediate:
context.executeCommand(command)→ executes immediately and records to history. - State-based:
context.action()→ executesstate.execute().
The rendering pipeline provides a structured, multi-backend architecture for drawing game worlds. It mirrors the core framework's entity model and introduces three domain abstractions — Universe, Planet, and Matter — that organize render state hierarchically.
public class Universe extends MatterThe top-level rendering context. A Universe holds a Context (the rendering context, separate from the game context) and an active Planet. When a planet is mounted via mountPlanet(planet), it is registered in the rendering context's entity list and set as the current state.
public abstract class Planet extends Entity<Relation>An abstract world entity with a managed lifecycle and a list of Matter objects. Every game world that participates in the render pipeline extends Planet (e.g. AshwakeRunWorld).
Lifecycle states: CREATED → RUNNING → PAUSED → CLOSED → EXITED
| Method | Description |
|---|---|
startPlanet() |
Transition to RUNNING. |
pausePlanet() / resumePlanet() |
Toggle pause. |
closePlanet() / restartPlanet() |
Close or restart. |
exitPlanet() |
Terminal state — cannot be restarted. |
canExecutePlanet() |
Returns true only when RUNNING. Subclasses check this in execute(). |
addMatter() / getMatters() |
Register render layers. |
public class Matter extends EntitiesGraph implements Iterable<Map.Entry<String, Params>>A render-parameter container that extends EntitiesGraph. Each Matter holds a LinkedHashMap<String, Params> of named render parameters (layer hint, material key, sort bias, etc.). Matters are iterated by the pipeline in registration order and represent logical render layers (backdrop, arena, actors, projectiles, HUD, etc.).
public abstract class RenderingPipelineThe abstract rendering pipeline. The render(Universe, Planet, RenderBackend) method drives the following sequence:
- Validate — null checks.
- Prepare lifecycle — auto-start created/closed planets.
- Bind — mount the planet in the universe.
- Begin frame —
backend.beginFrame(). - For each Matter —
beforeMatter()→ execute matchingRenderTaskobjects →afterMatter(). - End frame —
backend.endFrame().
DefaultRenderingPipeline extends this with a pluggable list of RenderTask objects.
@FunctionalInterface
public interface RenderTask {
void execute(RenderBackend backend, Universe universe, Planet planet, Matter matter);
}public interface RenderBackend {
String getBackendName();
// beginFrame, beforeMatter, afterMatter, endFrame
}Tasks are the unit of rendering work. Each task can query supports(backend) to skip incompatible backends. The backend interface abstracts the underlying graphics API.
public class SwingRenderingPanel extends JPanelA JPanel adapter that bridges the pipeline into Java2D. On every paintComponent() call, it creates a Java2DRenderBackend, invokes the pipeline, and disposes the graphics context. Subclasses override beforePipelineRender() / afterPipelineRender() for custom setup.
| Backend | Class | Platform |
|---|---|---|
| Java2D | Java2DRenderBackend |
Desktop (Swing) |
| OpenGL | OpenGLRenderBackend + LwjglOpenGLBridge |
Desktop (LWJGL 3.4.1) |
| OpenGL ES | OpenGLESRenderBackend + AndroidOpenGLESBridge / IosOpenGLESBridge |
Android / iOS |
| Metal | MetalRenderBackend + IosMetalBridge |
iOS |
| No-op | NoOpRenderBackend |
Testing / benchmarks |
A 2D physics simulation framework with 10 sub-packages. It follows the same pipeline architecture as the render system but operates on physical bodies instead of visual matters.
PhysicsScene mirrors Planet — an abstract entity with the same lifecycle states (CREATED → RUNNING → PAUSED → CLOSED → EXITED), a list of PhysicsMatter groups, and a configurable gravity vector (Vector2, default (0, 9.81)).
PhysicsSpace mirrors Universe — it mounts a PhysicsScene and provides the top-level simulation context.
public class PhysicsBody extends Entity<Relation>A rigid body with position, rotation, velocity, mass, collision layers, and a Form2 shape. Key features:
- MotionState: transform + linear velocity + force accumulator.
- Dynamic vs. static: setting mass to 0 makes the body static.
- Sensor mode: detects overlaps without physical response.
- Collision filtering: bitwise layer/mask system.
- Cached geometry: world-space mesh, contour, and bounds are lazily computed and invalidated on transform changes.
PhysicsMaterial holds friction and restitution (bounce) coefficients.
physics.geometry provides the 2D math primitives:
| Class | Description |
|---|---|
Vector2 |
Immutable 2D vector with add, subtract, multiply, dot, cross, normalize, distance, lerp. |
Transform2 |
Position + rotation + scale; apply(Vector2) transforms points. |
Bounds2 |
Axis-aligned bounding box with overlap/containment tests. |
Circle2 |
Center + radius. |
Quad2 |
Four-vertex quadrilateral. |
Triangle2 |
Three-vertex triangle. |
Mesh2 |
Triangle-soup mesh (list of Triangle2). |
Contour2 |
Ordered point loop for collision shapes. |
Triangulator2 |
Ear-clipping triangulation for contours → meshes. |
Geometry2 |
Static utilities (line intersection, point-in-triangle, etc.). |
physics.collision provides narrow-phase and query APIs:
| Class | Description |
|---|---|
CollisionDetector2 |
Full narrow-phase: circle–circle, circle–polygon, polygon–polygon (SAT), mesh–mesh. |
CollisionReport |
Result: hit flag, contact manifold, penetration depth, normal. |
ContactManifold / ContactPoint |
Contact geometry for resolution. |
OverlapCommand |
Boolean overlap query between two bodies. |
RaycastCommand / RaycastHit |
Ray-vs-body intersection with hit point, normal, distance. |
SweepCommand |
Swept-body collision test. |
physics.form provides shape definitions consumed by both collision and rendering:
| Form | Description |
|---|---|
CircleForm |
Circle shape. |
QuadForm |
Quad shape. |
TriangleForm |
Triangle shape. |
MeshForm |
Arbitrary triangle-mesh shape. |
CompositeForm |
Union of multiple sub-forms. |
All forms implement Form2, which provides toWorldMesh(), toWorldContour(), and getLocalBounds().
public abstract class PhysicsPipelineMirrors RenderingPipeline structure. The simulate(PhysicsSpace, PhysicsScene, deltaSeconds, PhysicsBackend) method drives:
- Validate → prepare lifecycle → bind.
- For each PhysicsMatter → for each PhysicsBody → execute
PhysicsTaskobjects. backend.step()→ after-simulation hooks.
| Backend | Description |
|---|---|
DeterministicPhysicsBackend |
Full simulation: integration, broadphase, narrowphase, resolution. |
SpatialHashBroadphase |
Grid-based spatial hashing for efficient pair generation. |
NoOpPhysicsBackend |
Silent backend for testing. |
FormComposer— programmatic shape composition (merge, offset, scale forms).FormMorpher— interpolate between two forms over time.AnchorPoint— named attachment points on forms.ContourTracer— trace contours from binary raster images (marching-squares).RasterShapeExtractor— full bitmap → physics-form pipeline with configurable options and profiles.PaletteExtractor— extract dominant color palettes from images.
A comprehensive asset cataloging and preparation system. The AssetManager entry point manages two catalogs:
- Entity bundles (
EntityAssetBundle) — image sequences, mesh data, and prepared GPU-ready assets keyed byAssetKey. - Draft libraries (
OpenGLDraftLibrary) — OpenGL material, texture, shader, palette, font, and UI module drafts.
Key sub-packages:
| Package | Purpose |
|---|---|
catalog/ |
AssetCatalog<T> — generic keyed registry. AssetKey — composite key. EntityAssetBundle — bundle of image frames + prepared meshes. |
manifest/ |
AssetManifestLoader — load manifests from disk. AssetManifestImporter — import external assets. AssetManifestValidator — validate manifest integrity. AssetAuthoringManifestAdapter — bridge manifests to the authoring pipeline. |
mesh/ |
ImageMeshBuilder — convert images to triangle meshes via contour tracing. EntityMeshSequence — ordered mesh frames for animation. |
pipeline/ |
AssetPreparationPipeline / DefaultAssetPreparationPipeline — run preparation tasks and produce AssetPreparationReport results. |
source/ |
ImageAssetSource — abstraction for image loading. Implementations: BufferedImageAssetSource, PathImageAssetSource. EntityImageSequence — ordered image frames. |
opengl/ |
Draft descriptors for OpenGL resources: materials, textures, shaders, palettes, fonts, UI elements, and UI modules. |
Cross-platform surface and input abstractions for mobile deployment.
| Class | Purpose |
|---|---|
IosSurface |
Interface: surface lifecycle + renderFrame(). |
IosMetalSurface |
Metal-backed surface with CAMetalLayer integration. |
IosOpenGLESSurface |
OpenGL ES-backed surface with EAGLContext. |
IosInputAdapter |
Touch event → game command adapter. |
IosTouchEvent / IosTouchPhase |
Touch data model. |
IosSafeAreaInsets |
Safe area insets for notched devices. |
IosInterfaceOrientation |
Portrait/landscape/flat orientation. |
IosMetalNativeBridge |
JNI-style bridge for Metal commands. |
| Class | Purpose |
|---|---|
MobileSurface |
Generic mobile surface interface. |
MobileOpenGLESSurface |
OpenGL ES surface with context management. |
MobileInputAdapter |
Touch event adapter. |
MobileTouchEvent / MobileTouchAction |
Touch data model (DOWN, MOVE, UP, CANCEL). |
public final class GameContextFactory {
public static Context create(Entity<? extends Relation> world)
}A static factory that replaces the per-game wiring that previously lived inside each launcher. It:
- Creates a new
Context. - Registers the world entity in the context's entity list and graph.
- Iterates over
world.getChildren()— for each child that is anEntity, it registers it in the entity list, adds it to the graph, and creates a bidirectional edge (world ↔ child). Non-entity children are added to the entity list only.
Both SnakeGameLauncher and TetrisGameLauncher call GameContextFactory.create(world) instead of duplicating this logic.
public class MainUnified entry point for both games:
| Command | Action |
|---|---|
java -cp out Main |
Launch Blockfall (default). |
java -cp out Main blockfall |
Launch Blockfall explicitly. |
java -cp out Main snake |
Launch Snake. |
java -cp out Main --smoke-test |
Run both smoke tests. |
java -cp out Main --smoke-test snake |
Run Snake smoke test only. |
java -cp out Main --smoke-test tetris |
Run Blockfall smoke test only. |
The isBlockfall() helper accepts both "tetris" and "blockfall" as aliases.
These classes implement the Snake game using the core framework.
public enum Direction {
UP(0, -1), DOWN(0, 1), LEFT(-1, 0), RIGHT(1, 0);
}Each constant carries a movement vector (dx, dy). The isOpposite(Direction) method checks whether two directions cancel out (e.g. UP ↔ DOWN), which prevents the snake from reversing into itself.
public class Food extends Entity<Relation>Entity ID: 102. Stores a single Point position. The relocate(Point) method repositions the food after the snake eats it. Defensively copies points to prevent mutation.
public class Snake extends Entity<Relation>Entity ID: 101. Manages the snake's body and movement:
| Field | Description |
|---|---|
body: Deque<Point> |
Ordered segments — head at front, tail at back. Initialized with 3 segments at the center of the board, extending leftward. |
direction |
The currently committed direction of travel. |
pendingDirection |
The direction the player has queued; committed on the next advance(). |
Key methods:
| Method | Description |
|---|---|
reset(x, y) |
Clear the body and place 3 segments at (x, y), (x-1, y), (x-2, y). Reset direction to RIGHT. |
queueDirection(dir) |
Accept a new direction unless it is null or the exact opposite of the current direction. |
nextHead() |
Compute the next head position by adding pendingDirection's delta to the current head. |
advance(nextHead, grow) |
Commit the pendingDirection, push a new head segment. If not growing (no food eaten), remove the tail. |
hitsSelf(candidate, growing) |
Check if candidate collides with any body segment. When not growing, the about-to-be-removed tail segment is excluded from the collision check. |
occupies(point) |
Check if any body segment occupies a given point (used for food spawning). |
getSegments() |
Return a defensive copy of the body as a List<Point>. |
public class SnakeWorld extends Entity<Relation>Entity ID: 100. The top-level game entity. Contains the Snake and Food as composite children.
| Field | Description |
|---|---|
width, height |
Board dimensions (default 20 × 20 in the GUI, 12 × 12 in the smoke test). |
random |
Random instance for food placement. Accepts a seeded Random for deterministic testing. |
snake, food |
Child entities. |
score |
Number of food items consumed. |
gameOver |
Set to true when the snake collides with a wall or itself. |
victory |
Set to true when the snake fills the entire board. |
Key methods:
| Method | Description |
|---|---|
execute() |
Overrides Entity.execute() — calls step(). The world itself is queued as a command each tick. |
step() |
Core game logic: compute next head, check wall/self collisions, advance snake, handle eating, and spawn new food. |
restart() |
Reset score, flags, snake position, and re-spawn food. |
spawnFood() |
Randomly place food on a tile not occupied by the snake. If the snake fills the entire board, trigger victory. |
public class DirectionCommand implements RelationA command that calls snake.queueDirection(direction). Queued via Context whenever the player presses a movement key. Does not directly move the snake — the direction is applied on the next SnakeWorld.step().
public class RestartSnakeCommand implements RelationA command that calls world.restart(). Queued via Context when the player presses R.
public class SnakeGamePanel extends JPanel implements ActionListener, KeyListenerThe Swing rendering and input component:
Constants:
| Constant | Value | Purpose |
|---|---|---|
CELL_SIZE |
28 px | Pixel size of each grid cell. |
HUD_HEIGHT |
88 px | Vertical space for the heads-up display. |
TICK_MS |
120 ms | Timer interval between game ticks (~8.3 FPS). |
Color Palette:
| Name | RGB | Used For |
|---|---|---|
SURFACE_TOP |
(14, 27, 31) |
Background gradient — top |
SURFACE_BOTTOM |
(22, 46, 42) |
Background gradient — bottom |
BOARD_DARK |
(24, 54, 48) |
Checkerboard dark cells |
BOARD_LIGHT |
(30, 66, 58) |
Checkerboard light cells |
SNAKE_HEAD |
(242, 197, 70) |
Golden head segment |
SNAKE_BODY |
(106, 201, 145) |
Green body segments |
FOOD |
(235, 94, 72) |
Red food circle |
HUD_TEXT |
(240, 245, 237) |
Primary HUD text |
HUD_MUTED |
(186, 199, 190) |
Secondary HUD text |
Rendering Pipeline (paintComponent → Graphics2D):
paintBackground()— Full-panel vertical gradient.paintHud()— Rounded semi-transparent card with title, controls hint, score, and snake length.paintBoard()— Checkerboard grid with rounded cells.paintFood()— Red circle with a small highlight for a 3D effect.paintSnake()— Rounded rectangles drawn back-to-front; head is golden, body is green.paintOverlay()— Game-over overlay with "Game Over" or "Board Cleared" plus a restart hint.
Anti-aliasing is enabled for both shape and text rendering.
public final class SnakeGameLauncherA static utility class that wires the Snake game:
launch(): CreatesSnakeWorld(20, 20), builds aContextviaGameContextFactory.create(), wrapsSnakeGamePanelin aJFrame.runSmokeTest(): Creates a 12 × 12 world withRandom(7L), queues a DOWN direction + 5 ticks, and returns a diagnostic string.
- Board: 20 × 20 grid (400 tiles), rendered as 28 × 28 px rounded cells in a checkerboard.
- Movement: One cell per tick (120 ms). Direction changes are queued — only one change per tick, preventing 180° reversals.
- Collision: Wall collision (leaving the grid) and self collision (hitting own body, excluding the tail when not growing) both end the game.
- Scoring: Each food item increments score by 1 and grows the snake by 1 segment. Food respawns on a random free tile.
- Victory: Filling every tile on the board ("Board Cleared").
Game loop:
Timer (120ms) → queue world → executeQueuedCommands → world.step() → repaint
Key press → queue DirectionCommand/RestartSnakeCommand → executeQueuedCommands → repaint
These classes implement the Blockfall (falling-block puzzle) game using the same core framework.
public enum TetrominoType { I, O, T, S, Z, J, L }Defines all 7 standard tetromino shapes. Each constant stores:
- A
Colorfor rendering. - A
int[4][4][2]array of 4 rotation states, where each rotation is 4 cells specified as[row, col]offsets.
| Piece | Color | RGB |
|---|---|---|
I |
Cyan | (78, 205, 196) |
O |
Yellow | (255, 209, 102) |
T |
Purple | (199, 125, 255) |
S |
Green | (120, 220, 82) |
Z |
Red | (255, 107, 107) |
J |
Blue | (94, 114, 228) |
L |
Orange | (255, 159, 67) |
The cells(int rotation) method returns the cell layout for a given rotation, wrapping via Math.floorMod.
public class FallingPiece extends Entity<Relation>Entity ID: 202. Represents the currently active falling piece:
| Field | Description |
|---|---|
type |
The TetrominoType of this piece. |
row, col |
Current grid position (top-left origin). |
rotation |
Current rotation state (0–3). |
Key methods:
| Method | Description |
|---|---|
reset(type, row, col) |
Set piece type, position, and reset rotation to 0. |
moveBy(dx, dy) |
Shift position by delta. |
moveTo(row, col) |
Set absolute position. |
setRotation(int) |
Set rotation, wrapped to [0, 3] via floorMod. |
cells() |
Return the 4 cell positions for the current state. |
cellsFor(row, col, rotation) |
Return cell positions for a hypothetical state (used for collision checking before committing). |
public class TetrisBoard extends Entity<Relation>Entity ID: 201. The locked-cell grid:
| Field | Description |
|---|---|
rows, cols |
Board dimensions (20 rows × 10 columns). |
lockedCells: TetrominoType[][] |
2D array — null means empty, non-null means a locked block of that type (used for per-type coloring). |
Key methods:
| Method | Description |
|---|---|
clear() |
Fill all cells with null. |
canPlace(piece) / canPlace(piece, row, col, rotation) |
Check if a piece fits at a position without colliding with walls or locked cells. |
lock(piece) |
Copy the piece's cells into the locked grid. |
clearFullLines() |
Remove completed rows, compact the grid downward, return the number of lines cleared. |
getCell(row, col) |
Return the TetrominoType at a position (or null). |
public class TetrisWorld extends Entity<Relation>Entity ID: 200. The top-level Blockfall entity. Contains TetrisBoard and FallingPiece as composite children.
| Field | Description |
|---|---|
board, activePiece |
Child entities. |
bag: List<TetrominoType> |
The 7-bag randomizer — shuffles all 7 types, draws until empty, then reshuffles. |
nextType |
The next piece to spawn (shown in the sidebar preview). |
score |
Accumulated score. |
linesCleared |
Total lines cleared. |
level |
Current level: 1 + (linesCleared / 10). |
paused, gameOver |
State flags. |
Key methods:
| Method | Description |
|---|---|
execute() |
Overrides Entity.execute() — calls tick(). |
tick() |
Gravity: try to move the piece down by 1. If it can't, lock it. |
moveHorizontal(dx) |
Shift the piece left or right (±1). |
softDrop() |
Move down 1 row, scoring +1 per row. If blocked, lock. |
hardDrop() |
Drop to the lowest valid row instantly, scoring +2 per row. Then lock. |
rotateClockwise() / rotateCounterClockwise() |
Rotate with a 6-position wall-kick table: {0,0}, {+1,0}, {-1,0}, {+2,0}, {-2,0}, {0,-1}. |
togglePause() |
Toggle pause state. |
restart() |
Clear board, reset bag, score, lines, level, spawn first piece. |
lockPiece() |
Lock the piece into the board, clear full lines, update scoring, spawn next piece. |
spawnPiece() |
Draw from bag, reset active piece to spawn position. If the piece can't be placed, set gameOver. |
getGhostCells() |
Compute where the piece would land if hard-dropped (for the ghost preview). |
getTickDelay() |
Dynamic tick speed: max(120, 620 - (level - 1) × 45) ms. Starts at 620 ms, speeds up by 45 ms per level, bottoms out at 120 ms. |
Scoring:
| Lines Cleared | Points |
|---|---|
| 1 | 100 × level |
| 2 | 300 × level |
| 3 | 500 × level |
| 4+ | 800 × level |
Soft drop: +1 per row. Hard drop: +2 per row.
public enum TetrisAction {
MOVE_LEFT, MOVE_RIGHT, ROTATE_CW, ROTATE_CCW,
SOFT_DROP, HARD_DROP, TOGGLE_PAUSE, RESTART
}Enumerates all player actions for Blockfall.
public class TetrisActionCommand implements RelationA command that dispatches a TetrisAction to the world via an enhanced switch expression:
case MOVE_LEFT → world.moveHorizontal(-1);
case MOVE_RIGHT → world.moveHorizontal(1);
case ROTATE_CW → world.rotateClockwise();
case ROTATE_CCW → world.rotateCounterClockwise();
case SOFT_DROP → world.softDrop();
case HARD_DROP → world.hardDrop();
case TOGGLE_PAUSE → world.togglePause();
case RESTART → world.restart();public class TetrisGamePanel extends JPanel implements ActionListener, KeyListenerThe Swing rendering and input component for Blockfall:
Constants:
| Constant | Value | Purpose |
|---|---|---|
CELL_SIZE |
30 px | Pixel size of each grid cell. |
PADDING |
24 px | Outer padding for the board. |
SIDEBAR_WIDTH |
210 px | Width of the right-side info panel. |
Color Palette:
| Name | RGB | Used For |
|---|---|---|
BG_TOP |
(13, 18, 28) |
Background gradient — top |
BG_BOTTOM |
(24, 30, 47) |
Background gradient — bottom |
BOARD_BG |
(12, 16, 24) |
Board surface |
GRID |
(36, 44, 62) |
Grid lines |
CARD |
(22, 28, 41, 220) |
Sidebar card background |
TEXT |
(244, 247, 255) |
Primary text |
MUTED |
(173, 181, 204) |
Secondary text |
GHOST |
(255, 255, 255, 48) |
Ghost piece preview |
Rendering Pipeline (paintComponent → Graphics2D):
paintBackground()— Full-panel vertical gradient.paintBoardSurface()— Rounded board area with grid lines.paintLockedBlocks()— Locked cells, colored byTetrominoType.paintGhostPiece()— Semi-transparent preview of where the piece will land.paintActivePiece()— The currently falling piece in its type color.paintSidebar()— Two rounded cards showing title, score/lines/level stats, next piece preview, and controls reference.paintOverlay()— "Paused" or "Run Over" overlay.
Each cell is rendered as a rounded rectangle with a subtle white border for a polished, glowing aesthetic.
public final class TetrisGameLauncherA static utility class that wires the Blockfall game:
launch(): CreatesTetrisWorld(), builds aContextviaGameContextFactory.create(), wrapsTetrisGamePanelin aJFrametitled "Blockfall".runSmokeTest(): Creates a world withRandom(11L), queues MOVE_LEFT → ROTATE_CW → HARD_DROP + 4 ticks, and returns a diagnostic string with score, lines, level, game-over state, and next piece.
- Board: 20 rows × 10 columns. Spawn position: row 0, column 3.
- Piece generation: 7-bag randomizer — all 7 types are shuffled, drawn one by one until the bag is empty, then reshuffled. Guarantees even distribution.
- Gravity: Each tick, the piece moves down by 1. If it can't, it locks and a new piece spawns.
- Wall kicks: Rotation attempts 6 offsets:
{0,0}, {+1,0}, {-1,0}, {+2,0}, {-2,0}, {0,-1}. The first valid position is used. - Line clearing: Completed rows are removed and all rows above compact downward.
- Speed scaling: Starts at 620 ms per tick (level 1). Each level reduces the interval by 45 ms. Minimum is 120 ms.
- Game over: When a newly spawned piece can't be placed at the spawn position.
Game loop:
Timer (dynamic delay) → queue world → executeQueuedCommands → world.tick() → update timer delay → repaint
Key press → queue TetrisActionCommand → executeQueuedCommands → update timer delay → repaint
A top-down arena-survival roguelite built on the core/ framework and the render/ pipeline. The player navigates through a fixed sequence of five rooms — each with distinct encounter types — collecting upgrades and defeating enemies culminating in a boss fight.
public class AshwakeRunWorld extends PlanetEntity ID: 9100. The top-level run entity. Extends Planet (from render/) to participate in the rendering pipeline. Contains AshwakePlayer and AshwakeRoomWorld as composite children.
| Field | Description |
|---|---|
player |
The player entity. |
roomWorld |
The current room container with all active game objects. |
roomSequence |
Fixed progression: Combat → Reward → Event → Elite → Boss. |
activeModifiers |
EnumSet of chosen run upgrades. |
kills, essence, score |
Run statistics. |
The world registers 8 Matter render layers: backdrop, arena, hazards, pickups, actors, projectiles, overlay, and HUD — each with layer hints, material keys, and sort biases.
Entity ID: 9101. The survivor entity with 3 resource bars:
| Resource | Max | Regen |
|---|---|---|
| Health | 120 | None (pickup only) |
| Ward | 40 | 5.5/s (absorbs damage first) |
| Energy | 100 | 22/s (used for dash + secondary) |
| Stat | Base Value |
|---|---|
| Move speed | 228 px/s |
| Projectile speed | 460 px/s |
| Attack interval | 0.23 s |
| Dash cooldown | 1.15 s, costs 22 energy |
| Dash speed | 720 px/s for 0.15 s |
| Critical chance | 8% |
| Dash i-frames | 0.20 s |
| Damage grace period | 0.36 s |
Six archetypes with distinct AI, all extending Entity<Relation>:
| Kind | HP | Behavior |
|---|---|---|
CHASER |
28 | Pursues the player directly. Deals contact damage. |
CASTER |
24 | Maintains distance. Fires CURSE_ORB projectiles. |
DASH_STRIKER |
34 | Telegraphs (0.34 s), then lunges at high speed. |
AREA_SEEDER |
42 | Orbits the player. Periodically drops hazard zones. |
SUMMONER |
58 | Keeps distance. Spawns pairs of chaser minions + fires shadow needles. |
BOSS |
360 | Fires 3-projectile fan spreads, spawns hazards + minions. Enrages below 45% HP. |
Elite-scaled enemies (in rooms 4+) receive ×1.45 HP, ×1.08 speed, ×1.2 damage.
Projectiles (AshwakeProjectile): 5 kinds with configurable speed, damage, lifetime, and pierce count.
| Kind | Radius | Source |
|---|---|---|
EMBER_BOLT |
6 | Player primary fire |
CURSE_ORB |
7 | Casters, empowered enemies |
SHADOW_NEEDLE |
4.5 | Summoners |
PULSE_SPARK |
5.5 | Chainburst radial ability |
BOSS_COMET |
10 | Boss attacks |
Hazards (AshwakeHazard): Area-of-effect zones — either friendly (from Shock Dash) or hostile (from enemies). Deal damage per second to targets within radius. Expire after a set lifetime.
Pickups (AshwakePickup): 3 types with a bobbing animation.
| Type | Effect |
|---|---|
HEALTH_ORB |
Restores health. |
EMBER_SHARD |
Adds essence (currency) + score. |
ENERGY_BLOOM |
Restores energy. |
| Room | Type | Description |
|---|---|---|
| 1 | Combat | 9+ enemies, 1 hazard. Clear all to advance. |
| 2 | Reward | Choose 1 of 3 modifier upgrades. |
| 3 | Event | Pickup shrine. Collect items, then advance. |
| 4 | Elite | Empowered summoner + 4 elite enemies + 2 hazards. |
| 5 | Boss | Boss + 2 support enemies. Defeat to win the run. |
Between rooms, the player chooses run modifiers from the AshwakeModifier enum:
| Modifier | Effect |
|---|---|
| Split Shot | Primary fire emits 2 angled side bolts. |
| Piercing Sigil | Projectiles pierce through 1 extra target. |
| Shock Dash | Dash leaves a damaging ember ring. |
| Ember Bloom | Defeated enemies drop more ember shards. |
| Ward Aura | Passive aura deals 14 DPS within 96 px radius. |
| Ritual Velocity | ×0.82 attack interval, ×1.16 projectile speed, ×1.06 move speed. |
| Critical Ash | +18% critical chance. |
| Chainburst | Every 5th attack erupts into an 8-projectile radial burst. |
All player actions flow through the command pipeline via 8 Relation implementations in AshwakeCommands.java:
AshwakeMoveCommand · AshwakeFireCommand · AshwakeDashCommand · AshwakeSecondaryCommand · AshwakeTogglePauseCommand · AshwakeRestartCommand · AshwakeAdvanceRoomCommand · AshwakeSelectModifierCommand
AshwakeBenchmark provides a headless profiling harness: 120 warmup frames + 720 measured frames, reporting per-frame update and render timings (avg, p95, max), peak entity counts, and run outcome.
Three scenarios:
| Scenario | CLI name | Setup |
|---|---|---|
| Projectile Hell | projectile-hell |
18 enemies (all types), 60 projectiles, 10 hazards, 6 pickups, all 6 modifiers active. Continuously replenished. |
| Boss Arena | boss-arena |
Boss + 6 elites, 24 boss comets, 6 hazards, 4 modifiers. |
| Reward Chamber | reward-chamber |
12 pickups, 4 friendly hazards, modifier selection UI. |
- Arena: 960 × 640 px continuous space (not grid-based).
- Tick rate: 60 FPS (16 ms timer,
1/60second delta). - Collision: Circle–circle overlap for all entity interactions.
- Movement: Continuous (WASD), velocity-based with impulse damping (0.86× per frame).
- Firing: Arrow-key directional aiming with automatic fire when keys held.
- Scoring: +35 per enemy, +500 per boss, +120 per room cleared, +45 per modifier, +3× essence value.
- Victory: Defeating the boss in room 5.
- Death: Health reaching 0.
Game loop:
Timer (16ms) → queue MoveCommand + FireCommand → queue world → executeQueuedCommands → world.update() → pipeline.render() → repaint
Key press → queue DashCommand/SecondaryCommand/PauseCommand/etc. → executeQueuedCommands → repaint
- JDK 21 or later (uses enhanced
switchwith arrow-case syntax and pattern matching). - No external runtime dependencies — Swing is included with the standard JDK.
- LWJGL 3.4.1 (bundled in
lib/lwjgl/3.4.1/) — required only for the OpenGL render backend; not needed for Java2D games.
From the repository root:
javac -d out *.java core/*.javaThis compiles all root source files and the core/ package into out/.
Ashwake depends on core/, render/, and platform/:
javac -d out_ashwake core/*.java render/*.java platform/ios/*.java platform/mobile/*.java ashwake/*.javaThis compiles into out_ashwake/.
This is the default game:
java -cp out MainYou can also launch it explicitly:
java -cp out Main blockfalljava -cp out Main snakeAshwake has its own launcher with a main() method:
java -cp out_ashwake ashwake.AshwakeLauncher| Key | Action |
|---|---|
↑ / W |
Move up |
↓ / S |
Move down |
← / A |
Move left |
→ / D |
Move right |
R |
Restart |
| Key | Action |
|---|---|
← / A |
Move piece left |
→ / D |
Move piece right |
↑ / W / X |
Rotate clockwise |
Z |
Rotate counter-clockwise |
↓ / S |
Soft drop |
Space |
Hard drop |
P |
Pause |
R |
Restart |
| Key | Action |
|---|---|
W / A / S / D |
Move survivor |
↑ / ↓ / ← / → |
Aim and fire (hold for auto-fire) |
Shift / Space |
Dash (costs 22 energy) |
Q |
Secondary ability — ashfire pulse (costs 40 energy) |
E / Enter |
Advance to next room |
1 / 2 / 3 |
Select modifier upgrade |
P |
Pause |
R |
Restart run |
Run both games' smoke tests:
java -cp out Main --smoke-testRun only Snake:
java -cp out Main --smoke-test snakeRun only Blockfall:
java -cp out Main --smoke-test tetrisExpected output (deterministic due to seeded Random):
Snake smoke test passed: head=6,11 score=0 length=3 gameOver=false
Blockfall smoke test passed: score=36 lines=0 level=1 gameOver=false next=L
java -cp out_ashwake ashwake.AshwakeLauncher --smoke-testRuns 420 ticks with seeded Random(17L), reporting room progress, kills, pickups, enemy/projectile counts, modifier count, and run state.
Ashwake includes a headless benchmark suite for profiling update and render performance under stress:
java -cp out_ashwake ashwake.AshwakeLauncher --benchmarkRun a single scenario:
java -cp out_ashwake ashwake.AshwakeLauncher --benchmark projectile-hell
java -cp out_ashwake ashwake.AshwakeLauncher --benchmark boss-arena
java -cp out_ashwake ashwake.AshwakeLauncher --benchmark reward-chamberEach scenario runs 120 warmup frames followed by 720 measured frames and reports:
- Update timing: avg, p95, max (ms).
- Render timing: avg, p95, max (ms).
- Peak entity counts: enemies, projectiles, hazards, pickups.
- Run outcome:
runOver/victory.
| Directory | Contents |
|---|---|
out/ |
Compiled .class files for Snake + Blockfall (root classes plus core/). |
out_ashwake/ |
Compiled .class files for Ashwake (under ashwake/, core/, render/, platform/). |
out_verify/ |
Verification build output. |
All output directories are generated by javac -d and can be safely deleted and regenerated.
The Kavunn Engine is intentionally generic. Here are some ways to extend the project:
| Extension | Approach |
|---|---|
| New game (simple) | Create a world entity extending Entity<Relation>, add child entities, wire via GameContextFactory.create(), build a panel and launcher. No core changes needed — Blockfall is the proof. |
| New game (render pipeline) | Extend Planet, register Matter layers, build a DefaultRenderingPipeline with render tasks, use SwingRenderingPanel. Ashwake is the proof. |
| New commands | Implement Relation (e.g. PauseCommand, SpeedChangeCommand). Queue via Context. |
| New entities | Extend Entity<Relation> (e.g. Wall, PowerUp). Register via GameContextFactory. |
| Add physics | Create a PhysicsScene subclass, attach PhysicsMatter groups with PhysicsBody objects, run through PhysicsPipeline.simulate(). |
| Custom render backend | Implement RenderBackend (e.g. Vulkan, WebGPU). The pipeline dispatches render tasks to any conforming backend. |
| Asset pipeline | Use AssetManager to register EntityAssetBundle objects, run prepareEntityAssets() to produce GPU-ready meshes. |
| Mobile deployment | Implement IosSurface or MobileSurface, wire through the appropriate OpenGLESBridge or MetalBridge. |
| Undo support | Context.commandHistory already stores executed commands. Implement an undo() method on Relation to support rollback. |
| Entity parameters | Use the inherited Params system: entity.add("speed", PrimaryTypeValue.integer(5)). |
| Graph queries | Use EntitiesGraph.getAdjacentVertices() to discover related entities at runtime. |
- Blockfall is intentionally harder than Snake: it adds gravity, rotation, line clearing, speed scaling, and stack pressure.
- Ashwake is the most complex game: it demonstrates the render pipeline, continuous physics, roguelite progression, and benchmark tooling.
- Main defaults to Blockfall. Ashwake has its own separate launcher (
AshwakeLauncher). - The render and physics pipelines follow a parallel architecture: both use validate → lifecycle → bind → iterate layers → dispatch tasks → finalize.
- The
physics/engine is framework-ready but not yet integrated into any game; Ashwake uses its own circle-overlap collision for simplicity.
This project is released under the Kavunn Engine Source-Available License. It is free for personal, educational, research, and other non-commercial use. Commercial use requires prior written permission.
See the full LICENSE file for details, or contact 📧 suleyman.artun@outlook.com for commercial licensing inquiries.
