Part of #560.
Today entities can only display images via atlas-sprites. The Sprite component carries a sprite_name resolved via TextureManager.findSprite, which only iterates atlases. There is no entity-side path for a standalone PNG to be displayed by the ECS render path. The workaround (wrap each loose image as a 1-sprite atlas) is ugly and bloats the resource list.
The AssetCatalog already has an image LoaderKind for standalone PNGs (decode-on-worker, upload-on-main, refcounted). What's missing is an entity component that consumes it.
Proposal
New Image component, separate from Sprite (chosen over extending Sprite for clarity — each component has one job).
v1 scope — deliberately narrow
- No
SpriteAnimation-style multi-frame animation.
- No
sprite_by_field-style dynamic name swapping.
- No sub-rect cropping.
If any of those needs arises, the answer is "use Sprite + an atlas." Image is for static single-PNG entities and nothing else.
project.labelle declaration
.resources accepts both atlas and standalone-image entries; .json becomes optional. Presence of .json selects the atlas loader, absence selects the catalog image loader.
.resources = .{
.{ .name = "rooms", .json = "assets/rooms.json", .texture = "assets/rooms.png" }, // atlas
.{ .name = "logo_splash", .texture = "assets/logo.png" }, // standalone
}
Inference walker
Walker scans strings in both Sprite.sprite_name and Image.name against a unified reverse index:
const ResourceRef = union(enum) {
atlas: []const u8, // bundle name (atlas containing the sprite)
image: []const u8, // asset name (the standalone image)
};
const reverse_index = std.StringHashMap(ResourceRef);
Hits route to the appropriate loader (legacy TextureManager for atlas; AssetCatalog for image). Coordinates with #563 (asset inference walker) and #565 (engine API decisions).
Naming caveat
There's already a gui_types.Image in the imgui-layer GUI types. The ECS Image component is distinct (different module, different render path). Worth a one-liner in docs to avoid confusion. If the collision is judged too risky during review, alternatives: Picture, LooseSprite, Standalone.
Renderer work
The renderer needs an Image render branch:
- Get the texture via
AssetCatalog.getTexture(name).
- Draw the full texture (no sub-rect, no atlas UV math).
- Honour pivot, layer, z_index, visibility — same semantics as
Sprite.
- Skip rendering when
assets.isReady(name) is false (matches the Q4 lazy pop-in model).
Part of #560.
Today entities can only display images via atlas-sprites. The
Spritecomponent carries asprite_nameresolved viaTextureManager.findSprite, which only iterates atlases. There is no entity-side path for a standalone PNG to be displayed by the ECS render path. The workaround (wrap each loose image as a 1-sprite atlas) is ugly and bloats the resource list.The
AssetCatalogalready has animageLoaderKindfor standalone PNGs (decode-on-worker, upload-on-main, refcounted). What's missing is an entity component that consumes it.Proposal
New
Imagecomponent, separate fromSprite(chosen over extendingSpritefor clarity — each component has one job).v1 scope — deliberately narrow
SpriteAnimation-style multi-frame animation.sprite_by_field-style dynamic name swapping.If any of those needs arises, the answer is "use
Sprite+ an atlas."Imageis for static single-PNG entities and nothing else.project.labelledeclaration.resourcesaccepts both atlas and standalone-image entries;.jsonbecomes optional. Presence of.jsonselects the atlas loader, absence selects the catalogimageloader.Inference walker
Walker scans strings in both
Sprite.sprite_nameandImage.nameagainst a unified reverse index:Hits route to the appropriate loader (legacy
TextureManagerfor atlas;AssetCatalogfor image). Coordinates with #563 (asset inference walker) and #565 (engine API decisions).Naming caveat
There's already a
gui_types.Imagein the imgui-layer GUI types. The ECSImagecomponent is distinct (different module, different render path). Worth a one-liner in docs to avoid confusion. If the collision is judged too risky during review, alternatives:Picture,LooseSprite,Standalone.Renderer work
The renderer needs an
Imagerender branch:AssetCatalog.getTexture(name).Sprite.assets.isReady(name)is false (matches the Q4 lazy pop-in model).