Implementation status¶
The rest of this documentation describes the engine as a whole. This page says which parts of it are built, so that nobody follows a guide for code that is not there yet.
Last verified: 2026-08-30, on master.
Packages¶
| Package | State | Notes |
|---|---|---|
good |
Working | The kernel is real and tested — ECS, memory pool, ring buffers, scheduler, scenes, hierarchy, input, assets, coroutines, timelines, GameView, on-screen sticks |
goo2d |
Working | Transforms, world transforms, screen space, camera, colliders, sprite rendering, grid-font text, debug draw, mouse picking |
good_audio_soloud |
Plays clips on five buses | The AudioBackend implementation, over SoLoud. Play, set a volume, stop. Bus levels and the voice budget are counted in good. No looping, loop points, fades or web |
good_cli |
Working | create, generate, assets compact, assets pack, build windows/linux/android/ios. Verified end to end |
goo2d_ffi_box2d |
Working | Box2D v3.1.1 vendored, shim written, bindings generated, builds on Windows/Linux/Android/macOS/iOS |
goo2d_physics_box2d |
Working | Bodies, colliders, the nine joints, effectors, raycast and overlap queries |
good_net |
Working | Messages, targets, channels, sessions, roster events and LoopbackNetTransport, with a conformance suite every backend is run against |
good_net_p2p |
Working on a LAN | A real UDP protocol — acks, retransmission, ordering, batching, fragmentation, keepalives — plus address-carrying join codes and LAN discovery. Does not cross the internet yet: that needs STUN and a rendezvous |
goo3d |
Transforms only | Transform3D, composed WorldTransform3D and Camera3D are real and tested. Nothing draws: there is no renderer, no mesh, no material and no light. Not on pub.dev, and good create --3d is still refused |
goo3d's siblings |
Not started | goo3d_physics_box3d and the rest. The kernel split exists so they land without touching the shared half |
Verified end to end¶
These were actually run:
good create→flutter analyzeclean →flutter rungood assets compacton a real image, with ffmpeg auto-downloadgood generateproducing a populatedTexturesenum, each value carrying the pixel size read from the image header, and aTextureSizeclass of the same numbers asstatic const intgood assets packwriting an encrypted chunk and the mappinggood build windowsproducing a launchable application whose bundle contains the chunk — and, withstrip-originalsunset, the loose copy beside it- The
NetTransportconformance suite against both backends — the in-process one and real UDP sockets - Two
Games in one process, each with its ownP2PNetTransport, playing a round over loopback UDP: a client request handled on the host, the host's answer handled on both - The reliable channel delivering 30 messages in order across a link throwing away 30% of everything
Not yet implemented¶
Deferred, and documented in place instead of left as silent gaps.
Engine¶
- Array-typed
DataDescriptorfields in the codegen path - Screen space beyond anchoring and view-fraction sizing.
ScreenTransform2Danchors an entity to a point on the view, sizes each axis in view units or as a fraction of the view, and draws it in a layer behind or in front of the world. Not there: parallax (a scroll factor against the camera), texture tiling with a UV origin, and the aspect-driven fit modes —auto,coverandcontain— which need a decoded texture's dimensions on the game isolate - Z-ordering beyond declaration order and the
zIndexsort - Skipping work for a subtree nothing moved. A sprite outside the camera's viewport is now dropped before it is queued, but a static subtree is still re-composed and re-tested every tick
- Dependency-based system ordering —
compareTois the mechanism today - On-screen controls beyond a stick.
JoystickArea,JoystickControlandJoystickdraw an analog stick and write the fourVirtualAxisfloats. Not there: a button, a d-pad, a trigger, and a layout that arranges a set of them for a phone. A button is aListeneraround the game's own art callingInputDevice.pressandrelease, so the missing half is the art and the arrangement, not a mechanism - Text beyond a grid font.
Text2Ddraws one line from a fixed-cell bitmap atlas you supply, laid out by arithmetic on the game isolate. Not there: proportional metrics, kerning, line wrapping, shaping,dart:uirasterisation, runtime font loading, and a font shipped with the engine. Screen-space text is not coming either — a HUD is widgets, and aText2Don aScreenTransform2Dentity is refused at declare time - Runtime inspection beyond debug draw.
debugDrawdraws lines, circles and labels over the world from a system, in world space, on its own buffer and its own budget, and aconst booltakes all of it out of a release build. Not there: the panel half — archetypes and their entity counts, one entity's column values, the system list with per-system tick cost.WorldCensusanswers the counting half on the game isolate already; nothing renders it, no component or field carries a name at runtime, and there is no per-system timing to report - A caller-owned buffer for same-tick reads. An ordinary read answers from
the published slot, which is what makes system order irrelevant. Code that
has to read back what it wrote earlier in the same tick reaches
DataPointer.readPendinginstead, and that is a second read path on the pointer with three column kinds behind it —float64,optInt64,optEntity— and a throw on every other. The engine has 47 calls to it, indata/hierarchy.dart,scene.dartand bothworld_transform.dartfiles. What is not there is a receiver whose type says which slot it reads, so the choice stops being a second method on the pointer. Most of the 47 read what another caller wrote earlier in the same tick, so a buffer scoped to one caller does not serve them, andreadPendingcannot be deleted (#258) goo2d_physics_box2d's rigid-body helpers are still on the mixin.RigidBody2D.applyForce,applyImpulse,applyTorque,setVelocity,setAngularVelocityandsetSimulatedtake the entity as an argument (body.applyImpulse(entity, ix, iy)).Transform2DandTransform3Dare both on the accessor form the guide teaches; these six are the last group that is not (#87).- Audio beyond levels and a budget. A clip plays and stops through
state.audio, and a playing voice holds a claim on its asset so a track survives the scene that started it unloading. Five buses - master, music, effects, dialogue, interface - each carry a level,setLevelreaches the voices already sounding, andGame.maxVoicesPerBuscaps each bus separately and stops that bus's oldest voice to seat a new one. The budget is counted in the engine because no backend supplies one: flutter_soloud 4.1.7'ssetMaxActiveVoiceCountcaps how many voices are mixed per buffer and not how many may be alive, so a cap of 4 reads back as 4 and then permits 59 concurrent voices. What is not there: looping and authored loop points (an asset-pipeline change before it is a mixer change - there is nowhere to author a loop point today), fades, a settings surface that persists the levels, positional audio, and web. - The 3D renderer.
goo3dcomposes transforms and carries a camera; the half that draws is a native backend behind our own C shim and is not written.Renderable3D,MeshAsset,MaterialAssetandLight3Ddo not exist yet, so the 3D rendering guide describes them ahead of the code.
Tooling¶
- Struct-layout codegen.
good generatewrites the asset bindings. Hoisting the runtimeDataDescriptorlayout algorithm to build time by scanningComponent/EntityStructwithpackage:analyzeris a separate and much larger piece of work, and emitting a stub would make it look done. good build macos— run the pipeline steps andflutter build macos.good run— useflutter run.- A texture size the generator could not read.
good generateparses PNG, WebP, GIF, BMP and JPEG headers. Anything else with a texture extension generates0for its width and height, named in the run's output. An SVG or an AVIF is the case that reaches this.
Networking¶
- Crossing the internet.
good_net_p2pjoin codes carry the host's address, which is enough on one machine or one LAN and not enough through a home router. STUN (learning a peer's public address) and a rendezvous (swapping those addresses so both sides punch at once) are the next landing. - A relay fallback. Hole punching fails when both peers are behind symmetric NAT. "Free, no server" and "always connects" are not the same claim, and a TURN-style relay is a separate, clearly-scoped addition.
- Request/reply messages. Not provided — see the networking guide.
- The ECS replication layer — a
Replicatedmixin, delta compression, prediction and reconciliation.good_netmoves declared records; replication is built on top of that, and the channel split is the primitive it needs. - Congestion control. A link sends what the game asks it to and reports
packetLossso the game can decide to send less. Approximating a congestion controller would be worse than naming it as missing.
Platforms¶
- Web. The kernel uses
dart:ffifor storage and spawns an isolate for the simulation; neither exists there.good_net_p2padditionally needsdart:iosockets.
The Steam backend was dropped
good_net_steam was removed instead of built. The transport contract is
open, so a Steam backend remains possible as a separate package; nothing in
good_net assumes one. good_ffi_steamworks, the empty bindings package it
would have been built on, went with it.
Publishing¶
All seven packages are on pub.dev at 0.1.0, under BSD 3-Clause. Pin
^0.1.0; the long-dead goo2d 0.0.2 still resolves for ^0.0.1.
Known rough edges¶
Things that work but will catch you out:
good createkeeps Flutter'smain.dart.flutter createruns first and writes its counter app; the scaffolder never overwrites an existing file, so the goodmain.dartis skipped and the log saysKept existing lib/main.dart. Delete it and re-run with--no-flutter-create.- Re-running
good createafter editing the pubspec duplicates keys. The idempotence check matches the literal line it wrote, so an edited dependency line slips past it and you get twogoo2d:entries and twoassets:blocks. test/widget_test.dartfromflutter createreferencesMyApp, which no longer exists oncemain.dartis the good one.
Contributing to this page¶
When something in the "not yet implemented" list lands, move it up and delete the entry — and when a page elsewhere in these docs starts being true, nothing needs to change there, because it was already written as though it were.