Merge pull request 'dev/studio-packer-rgba8888-asset-pipeline' (#11) from dev/studio-packer-rgba8888-asset-pipeline into master
All checks were successful
JaCoCo Coverage #### Project Overview
No changes detected, that affect the code coverage.
* Line Coverage: 61.03% (17116/28043)
* Branch Coverage: 51.80% (6569/12681)
* Lines of Code: 28043
* Cyclomatic Complexity: 11219
#### Quality Gates Summary
Output truncated.
Test / Build skipped: 16, passed: 593
Intrepid/Prometeu/Studio/pipeline/head This commit looks good
Reviewed-on: #11
@ -1,4 +1,5 @@
|
||||
{"type":"meta","next_id":{"DSC":38,"AGD":41,"DEC":37,"PLN":81,"LSN":53,"CLSN":1}}
|
||||
{"type":"meta","next_id":{"DSC":39,"AGD":42,"DEC":38,"PLN":87,"LSN":53,"CLSN":1}}
|
||||
{"type":"discussion","id":"DSC-0038","status":"open","ticket":"studio-packer-rgba8888-asset-pipeline","title":"Studio and Packer RGBA8888 Asset Pipeline Alignment","created_at":"2026-05-23","updated_at":"2026-05-23","tags":["studio","packer","assets","glyph-bank","palette","rgba8888","runtime-alignment"],"agendas":[{"id":"AGD-0041","file":"AGD-0041-studio-packer-rgba8888-asset-pipeline.md","status":"accepted","created_at":"2026-05-23","updated_at":"2026-05-23"}],"decisions":[{"id":"DEC-0037","file":"DEC-0037-studio-packer-rgba8888-asset-pipeline.md","status":"accepted","created_at":"2026-05-23","updated_at":"2026-05-23","ref_agenda":"AGD-0041"}],"plans":[{"id":"PLN-0081","file":"PLN-0081-rgba8888-packer-and-studio-spec-propagation.md","status":"done","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0037"]},{"id":"PLN-0082","file":"PLN-0082-rgba8888-palette-model-and-argb-conversion.md","status":"done","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0037"]},{"id":"PLN-0083","file":"PLN-0083-rgba8888-glyph-payload-emission-and-sizes.md","status":"done","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0037"]},{"id":"PLN-0084","file":"PLN-0084-rgba8888-packer-projections-and-workspace-apis.md","status":"done","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0037"]},{"id":"PLN-0085","file":"PLN-0085-rgba8888-tests-fixtures-and-conformance.md","status":"done","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0037"]},{"id":"PLN-0086","file":"PLN-0086-rgba8888-final-integration-and-residue-removal.md","status":"done","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0037"]}],"lessons":[]}
|
||||
{"type":"discussion","id":"DSC-0037","status":"done","ticket":"pbs-autocomplete-parameter-names","title":"PBS autocomplete parameter names for stdlib and method calls","created_at":"2026-05-08","updated_at":"2026-05-14","tags":["compiler-pbs","studio","lsp","autocomplete","signature-help","stdlib"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0052","file":"discussion/lessons/DSC-0037-pbs-autocomplete-parameter-names/LSN-0052-canonical-callable-parameter-names-through-pbs-editor-assistance.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14"}]}
|
||||
{"type":"discussion","id":"DSC-0036","status":"open","ticket":"pbs-symbol-documentation-and-hover-markdown","title":"Modelo de documentacao de simbolos em PBS e consumo markdown no hover","created_at":"2026-05-08","updated_at":"2026-05-08","tags":["compiler","compiler-pbs","studio","lsp","vscode","editor","hover","documentation","markdown"],"agendas":[{"id":"AGD-0039","file":"AGD-0039-pbs-symbol-documentation-and-hover-markdown.md","status":"open","created_at":"2026-05-08","updated_at":"2026-05-08"}],"decisions":[],"plans":[],"lessons":[]}
|
||||
{"type":"discussion","id":"DSC-0035","status":"done","ticket":"pbs-lsp-editor-assistance-wave-1","title":"Wave 1 de assistencia editorial via LSP para PBS no VS Code","created_at":"2026-05-08","updated_at":"2026-05-08","tags":["studio","lsp","vscode","compiler","compiler-pbs","editor","completion","hover","signature-help"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0051","file":"discussion/lessons/DSC-0035-pbs-lsp-editor-assistance-wave-1/LSN-0051-compiler-backed-editor-assistance-for-pbs.md","status":"done","created_at":"2026-05-08","updated_at":"2026-05-08"}]}
|
||||
|
||||
@ -0,0 +1,273 @@
|
||||
---
|
||||
id: AGD-0041
|
||||
ticket: studio-packer-rgba8888-asset-pipeline
|
||||
title: Agenda - Studio and Packer RGBA8888 Asset Pipeline Alignment
|
||||
status: accepted
|
||||
created: 2026-05-23
|
||||
resolved: 2026-05-23
|
||||
decision: DEC-0037
|
||||
tags:
|
||||
- studio
|
||||
- packer
|
||||
- assets
|
||||
- glyph-bank
|
||||
- palette
|
||||
- rgba8888
|
||||
- runtime-alignment
|
||||
---
|
||||
|
||||
## Pain
|
||||
|
||||
Domain owner: `studio`, with direct subdomain ownership in `packer` and
|
||||
cross-domain dependency on `runtime`.
|
||||
|
||||
The runtime RGBA8888 migration changes the color contract underneath the Studio
|
||||
asset pipeline. Runtime `DEC-0029` says RGBA8888 is the single canonical logical
|
||||
color and physical framebuffer format, and that asset packages must encode
|
||||
palettes as RGBA8888 without preserving RGB565 compatibility.
|
||||
|
||||
Studio still carries RGB565 in the packer-facing asset pipeline:
|
||||
|
||||
- `docs/specs/packer/4. Build Artifacts and Deterministic Packing Specification.md`
|
||||
defines `GLYPH/indexed_v1` palette payloads as `64 * 16 * 2` bytes of
|
||||
little-endian `RGB565` values.
|
||||
- `PackerPaletteV1` stores both `originalArgb8888` and `convertedRgb565`.
|
||||
- `PackerGlyphBankWalker` drops partial alpha, treats alpha 0 and magenta as
|
||||
transparent index `0`, stores only RGB identity for palette lookup, and
|
||||
converts palette colors to RGB565.
|
||||
- `PackerAssetDetailsService`, `PackerReadMessageMapper`, test fixtures, and
|
||||
project `asset.json` files expose `convertedRgb565` as normal pipeline data.
|
||||
|
||||
The problem is not only changing one serializer. The Studio/packer side must
|
||||
decide how authoring metadata, palette UI/data, emitted `assets.pa`, tests, and
|
||||
existing project fixtures align with runtime RGBA channel order and meaningful
|
||||
alpha.
|
||||
|
||||
## Context
|
||||
|
||||
Runtime discussion context:
|
||||
|
||||
- `../runtime/discussion/workflow/agendas/AGD-0037-rgba8888-framebuffer-and-pixel-format-direction.md`
|
||||
was consulted as the originating discussion.
|
||||
- `../runtime/discussion/workflow/decisions/DEC-0029-rgba8888-runtime-pixel-format-contract.md`
|
||||
is accepted and normative for runtime.
|
||||
- Runtime plans already split execution across specs, HAL/ABI, renderer,
|
||||
asset palettes/tooling, host presentation, and validation.
|
||||
|
||||
Relevant runtime invariants for this Studio discussion:
|
||||
|
||||
- raw color values use RGBA channel order;
|
||||
- asset package palettes must be RGBA8888;
|
||||
- old RGB565 palette assets are not compatible runtime input;
|
||||
- indexed tile/sprite payloads may remain indexed/paletized;
|
||||
- transparency is represented by alpha in the resolved palette entry, not by
|
||||
reserving palette index `0` as a special transparent index;
|
||||
- RGB565 helpers may exist only as temporary migration utilities, not the
|
||||
normal runtime path.
|
||||
|
||||
Relevant Studio/packer state:
|
||||
|
||||
- `assets.pa` is the authoritative runtime-facing artifact owned by the packer.
|
||||
- `output.pipeline` is tooling-only by default, but current glyph palette data
|
||||
lives there and is used by packer details/read flows.
|
||||
- `GLYPH/indexed_v1` currently emits a fixed 256x256 packed `u4` pixel plane
|
||||
plus a fixed 64-palette RGB565 block.
|
||||
- `DSC-0005` is still open around variable tile-bank palette serialization.
|
||||
That discussion optimizes fixed-vs-variable palette count. RGBA8888 changes
|
||||
the color encoding and transparency semantics first, so it should supersede
|
||||
or reframe any RGB565 assumptions before variable palette work continues.
|
||||
- Studio scene publication currently carries `palette_id` in scene pack
|
||||
requests. Scene payload shape is mostly unaffected by color format, but scene
|
||||
previews, palette defaults, validation, and any editor-visible palette data
|
||||
must remain consistent with the glyph-bank contract.
|
||||
|
||||
## Open Questions
|
||||
|
||||
- [x] Should Studio keep the runtime-facing format name `GLYPH/indexed_v1` and
|
||||
make it an incompatible v1 contract update, or should packer emit a new
|
||||
explicit format/version such as `GLYPH/indexed_rgba8888_v1`?
|
||||
- Keep `GLYPH/indexed_v1`. The structural change is in palette encoding; the
|
||||
remaining glyph payload model stays stable.
|
||||
- [x] What is the canonical authoring-side palette field name: keep
|
||||
`originalArgb8888`, rename to `rgba8888`, or store structured channel data?
|
||||
- Use `rgba8888` unless the decision stage finds a concrete tooling reason to
|
||||
choose a more explicit variant.
|
||||
- [x] How should Java `BufferedImage` ARGB input be converted to runtime RGBA
|
||||
raw values without confusing storage order, JSON numeric representation, or
|
||||
signed `int` display in tooling?
|
||||
- Convert ARGB input to RGBA output directly at the packer boundary.
|
||||
- [x] Does palette index `0` remain an ordinary palette slot immediately, as
|
||||
runtime `DEC-0029` requires, or does Studio need a temporary migration step to
|
||||
rewrite existing transparent-index assets into alpha-bearing palette entries?
|
||||
- Palette index `0` becomes an ordinary palette slot. Glyph palettes therefore
|
||||
have 16 usable colors, not 15 plus a reserved transparent index.
|
||||
- [x] Should magenta color-key support disappear from Studio authoring now, or
|
||||
survive only as an explicit import/conversion helper outside the normal pack
|
||||
path?
|
||||
- Magenta color-key support should disappear completely.
|
||||
- [x] Should partial alpha in PNG tiles become first-class palette data, or
|
||||
should the packer still reject/diagnose partial alpha until renderer behavior
|
||||
is fully proven?
|
||||
- Partial alpha becomes valid palette data now. Existing validations and
|
||||
warnings that flatten or reject partial alpha should be removed.
|
||||
- [x] Which existing fixtures and sample projects should be regenerated versus
|
||||
deleted because they encode RGB565-specific expectations?
|
||||
- `main` and `fragments` will be regenerated manually later; they should not
|
||||
block the Studio/packer decision.
|
||||
- [x] How should this agenda relate to `DSC-0005`: close it as superseded,
|
||||
revise it after RGBA8888, or keep it open as a later palette-count
|
||||
optimization?
|
||||
- `DSC-0005` remains a later step. RGBA8888 should not absorb variable
|
||||
palette-count serialization.
|
||||
|
||||
## Options
|
||||
|
||||
### Option A - Minimal serializer swap inside current names
|
||||
|
||||
- **Approach:** Keep current public names and `GLYPH/indexed_v1`, replace the
|
||||
palette block with fixed `64 * 16 * 4` RGBA8888 entries, remove
|
||||
`convertedRgb565`, and update tests/specs in place.
|
||||
- **Pro:** Smallest naming churn and likely the fastest path to unblock runtime
|
||||
asset decode.
|
||||
- **Con:** The `v1` label would silently change binary meaning, which is risky
|
||||
for fixtures, docs, and any stale build output.
|
||||
- **Maintainability:** Medium. Fast, but it relies on the repository accepting a
|
||||
coordinated breaking change with no compatibility ambiguity.
|
||||
|
||||
### Option B - Explicit RGBA8888 glyph payload format
|
||||
|
||||
- **Approach:** Add an explicit runtime-facing glyph format/version for the
|
||||
RGBA8888 palette payload, retire RGB565 output from the normal pack path, and
|
||||
update asset declarations, specs, materialization, tests, fixtures, and sample
|
||||
projects to the new format.
|
||||
- **Pro:** Makes the breaking contract visible in file format names, tests, and
|
||||
diagnostics while still obeying the runtime decision not to support RGB565 as
|
||||
compatible input.
|
||||
- **Con:** More code and fixture churn; requires clear migration handling for
|
||||
stale project declarations and generated build artifacts.
|
||||
- **Maintainability:** Strong. The format boundary becomes explicit and easier
|
||||
to audit.
|
||||
|
||||
### Option C - Broaden the wave into full palette model redesign
|
||||
|
||||
- **Approach:** Use the RGBA8888 migration to redesign palette count, sparse vs
|
||||
dense indices, material palettes, import/export, alpha authoring UI, and scene
|
||||
palette defaults in one decision.
|
||||
- **Pro:** Could produce a cleaner long-term asset model and resolve `DSC-0005`
|
||||
at the same time.
|
||||
- **Con:** Mixes a mandatory runtime alignment with optional model improvements,
|
||||
increasing risk and delaying the renderer/asset migration.
|
||||
- **Maintainability:** Weak for this wave. Better reserved for follow-up after
|
||||
the RGBA8888 contract is stable.
|
||||
|
||||
## Discussion
|
||||
|
||||
The discussion converged toward an in-place `GLYPH/indexed_v1` structural
|
||||
update rather than a new format name. This keeps the glyph-bank identity stable:
|
||||
indexed pixels, fixed emitted sheet shape, scene `palette_id` references, and
|
||||
the broad runtime glyph-bank model remain the same. The incompatible change is
|
||||
the palette block and palette metadata moving from RGB565-derived values to raw
|
||||
RGBA8888.
|
||||
|
||||
The authoring/read projection should expose `rgba8888` as the canonical palette
|
||||
field. Java image inputs may still arrive as ARGB through `BufferedImage`, but
|
||||
that is an input representation only. The packer boundary converts ARGB to
|
||||
runtime RGBA and the rest of the pipeline should speak RGBA8888.
|
||||
|
||||
Palette index `0` is no longer special. It becomes a normal usable palette
|
||||
entry, which gives glyph palettes 16 usable colors. Transparency comes from the
|
||||
alpha byte in the RGBA8888 palette value. As a consequence, the old magenta
|
||||
color-key behavior should be removed completely, not retained as a normal
|
||||
authoring shortcut or hidden compatibility rule.
|
||||
|
||||
Partial alpha is valid data in this model. The packer should preserve it in the
|
||||
palette instead of warning, flattening, or rejecting it. Existing validations
|
||||
around partial alpha and the 15-color limit are therefore stale and should be
|
||||
rewritten around the new 16-entry RGBA palette contract.
|
||||
|
||||
Existing sample projects and fixtures that still carry RGB565 assumptions are
|
||||
not part of the discussion blocker. `main` and `fragments` will be regenerated
|
||||
later. `DSC-0005` remains a separate future discussion for palette-count
|
||||
serialization and should not be folded into this RGBA8888 wave.
|
||||
|
||||
## Tradeoffs
|
||||
|
||||
### Compatibility vs explicit break
|
||||
|
||||
Runtime `DEC-0029` already rejects RGB565 compatibility for this migration.
|
||||
Studio therefore should not preserve RGB565 input as a normal path. The open
|
||||
tradeoff is whether the packer should make the break visible through a new
|
||||
format/version or simply mutate the existing v1 meaning.
|
||||
|
||||
### Authoring convenience vs runtime truth
|
||||
|
||||
Java image tooling naturally reads pixels as ARGB ints. Runtime wants raw RGBA
|
||||
channel order. Studio can keep ARGB only as an import-side implementation detail,
|
||||
but packer output, public metadata, and tests need one unambiguous RGBA8888
|
||||
representation.
|
||||
|
||||
### Indexed art identity vs alpha semantics
|
||||
|
||||
The indexed/palette model can remain. What changes is that every palette entry,
|
||||
including index `0`, should be a real RGBA color. Transparent pixels should be
|
||||
encoded by alpha, not by a hardwired index rule or magenta color key.
|
||||
|
||||
### Scope control vs complete cleanup
|
||||
|
||||
It is tempting to combine RGBA8888 with variable palette serialization from
|
||||
`DSC-0005`. That should be resisted unless the decision explicitly expands
|
||||
scope. RGBA8888 changes bytes per palette entry and transparency semantics;
|
||||
palette count optimization is a separate axis.
|
||||
|
||||
## Recommendation
|
||||
|
||||
Adopt a refined Option A.
|
||||
|
||||
The Studio/packer decision should keep `GLYPH/indexed_v1` as the runtime-facing
|
||||
format name, but redefine its palette block and palette metadata around
|
||||
RGBA8888. This is an intentional structural break in the palette contract, not
|
||||
a new glyph-bank format.
|
||||
|
||||
The recommended shape for the decision is:
|
||||
|
||||
1. emit palette entries as raw RGBA8888 values in runtime RGBA channel order;
|
||||
2. replace `convertedRgb565` with canonical `rgba8888` data in authoring and
|
||||
read/detail projections;
|
||||
3. treat Java/PNG ARGB as an input decoding detail and convert deliberately at
|
||||
the packer boundary;
|
||||
4. make palette index `0` ordinary, yielding 16 usable palette entries;
|
||||
5. represent transparency and partial transparency only through RGBA alpha;
|
||||
6. remove magenta color-key behavior completely;
|
||||
7. remove partial-alpha flattening/rejection and preserve partial alpha as
|
||||
valid palette data;
|
||||
8. preserve fixed 64-palette serialization for this wave unless a decision
|
||||
explicitly revises `DSC-0005`;
|
||||
9. regenerate or rewrite RGB565 fixtures instead of supporting stale RGB565
|
||||
assets at runtime.
|
||||
|
||||
## Next Step
|
||||
|
||||
The agenda is ready to move to decision if the refined recommendation is
|
||||
accepted.
|
||||
|
||||
The next artifact should be a Studio/packer decision that references runtime
|
||||
`DEC-0029`, leaves `DSC-0005` for a later palette-count step, and defines
|
||||
propagation targets for:
|
||||
|
||||
- `docs/specs/packer`;
|
||||
- `docs/specs/studio`;
|
||||
- `prometeu-packer` models, walkers, serializers, services, fixtures, and
|
||||
tests;
|
||||
- sample project assets;
|
||||
- any Studio asset workspace UI/read projections that expose palette data.
|
||||
|
||||
## Resolution
|
||||
|
||||
Accepted on 2026-05-23.
|
||||
|
||||
The discussion is closed in favor of decision `DEC-0037`. The accepted
|
||||
direction keeps `GLYPH/indexed_v1`, moves its palette contract to RGBA8888,
|
||||
uses `rgba8888` as the canonical palette field, makes palette index `0`
|
||||
ordinary, removes magenta color-key behavior completely, accepts partial alpha
|
||||
as valid palette data, and leaves `DSC-0005` for a later palette-count
|
||||
serialization step.
|
||||
@ -0,0 +1,228 @@
|
||||
---
|
||||
id: DEC-0037
|
||||
ticket: studio-packer-rgba8888-asset-pipeline
|
||||
title: Studio and Packer RGBA8888 Asset Pipeline Contract
|
||||
status: accepted
|
||||
created: 2026-05-23
|
||||
accepted: 2026-05-23
|
||||
ref_agenda: AGD-0041
|
||||
plans:
|
||||
- PLN-0081
|
||||
- PLN-0082
|
||||
- PLN-0083
|
||||
- PLN-0084
|
||||
- PLN-0085
|
||||
- PLN-0086
|
||||
tags:
|
||||
- studio
|
||||
- packer
|
||||
- assets
|
||||
- glyph-bank
|
||||
- palette
|
||||
- rgba8888
|
||||
- runtime-alignment
|
||||
---
|
||||
|
||||
## Status
|
||||
|
||||
Accepted. Drafted from accepted agenda `AGD-0041` and accepted on
|
||||
2026-05-23.
|
||||
|
||||
## Context
|
||||
|
||||
Runtime `DEC-0029` makes RGBA8888 the canonical logical color representation
|
||||
and physical framebuffer format for Prometeu. It also requires asset package
|
||||
palettes to use RGBA8888, rejects RGB565 compatibility as normal runtime input,
|
||||
and defines transparency through alpha rather than a reserved palette index.
|
||||
|
||||
Studio embeds the packer and owns the asset authoring and publication pipeline
|
||||
that emits `assets.pa`. Before this decision, the Studio/packer glyph-bank path
|
||||
still carried RGB565 assumptions:
|
||||
|
||||
- `GLYPH/indexed_v1` palette payloads were specified as fixed
|
||||
`64 * 16 * 2` RGB565 `u16` values.
|
||||
- `PackerPaletteV1` exposed `originalArgb8888` and `convertedRgb565`.
|
||||
- `PackerGlyphBankWalker` flattened partial alpha, treated alpha zero and
|
||||
magenta as transparent index `0`, limited authored colors to 15 usable
|
||||
entries, and converted colors to RGB565.
|
||||
- Studio/packer read projections, details APIs, fixtures, and sample assets
|
||||
exposed `convertedRgb565` as ordinary pipeline data.
|
||||
|
||||
The runtime decision is already normative for the color contract. This decision
|
||||
locks how Studio and packer align their glyph-bank asset pipeline with it.
|
||||
|
||||
## Decision
|
||||
|
||||
Studio and packer SHALL keep the runtime-facing glyph payload format name
|
||||
`GLYPH/indexed_v1`.
|
||||
|
||||
The `GLYPH/indexed_v1` glyph-bank model SHALL remain indexed/paletized. Indexed
|
||||
pixel payloads, fixed emitted sheet shape, scene `palette_id` references, and
|
||||
the broad glyph-bank identity remain stable.
|
||||
|
||||
The structural change is the palette contract. `GLYPH/indexed_v1` palette data
|
||||
MUST move from RGB565-derived values to RGBA8888 values in runtime RGBA channel
|
||||
order.
|
||||
|
||||
Packer output, packer metadata, Studio read/detail projections, tests, and
|
||||
fixtures MUST stop treating RGB565 as the normal palette representation.
|
||||
`convertedRgb565` MUST be removed from the normal authored and projected
|
||||
palette contract.
|
||||
|
||||
The canonical authored/projected palette field for this wave SHALL be
|
||||
`rgba8888`.
|
||||
|
||||
Java `BufferedImage` and PNG inputs MAY be read as ARGB by host APIs, but ARGB
|
||||
is only an input representation. The packer boundary MUST convert ARGB input to
|
||||
runtime RGBA8888 output deliberately before emitting or projecting canonical
|
||||
palette data.
|
||||
|
||||
Palette index `0` MUST be an ordinary palette index. Glyph palettes SHALL have
|
||||
16 usable entries, not 15 usable entries plus a reserved transparent index.
|
||||
|
||||
Transparency and partial transparency MUST be represented only by the alpha
|
||||
channel in the RGBA8888 palette value.
|
||||
|
||||
Magenta color-key behavior MUST be removed completely from the normal Studio
|
||||
and packer asset pipeline. Magenta MUST NOT remain a hidden compatibility rule,
|
||||
normal authoring shortcut, or runtime-facing transparency encoding.
|
||||
|
||||
Partial alpha in PNG/glyph inputs SHALL be valid palette data. Packer behavior
|
||||
that warns about, flattens, rejects, or otherwise strips partial alpha MUST be
|
||||
removed or rewritten around the RGBA8888 palette contract.
|
||||
|
||||
The fixed 64-palette serialization model remains in scope for this wave unless
|
||||
another accepted decision explicitly revises it. `DSC-0005` remains a later
|
||||
palette-count serialization discussion and MUST NOT be folded into this
|
||||
RGBA8888 migration by implication.
|
||||
|
||||
Existing `main` and `fragments` sample project assets may be regenerated
|
||||
manually later. Their regeneration is not a blocker for this decision and MUST
|
||||
NOT justify preserving RGB565 compatibility in the packer.
|
||||
|
||||
## Rationale
|
||||
|
||||
Keeping `GLYPH/indexed_v1` avoids renaming a format whose core identity remains
|
||||
the same: indexed glyph pixels resolved through palettes. The incompatible part
|
||||
is specifically the palette encoding and transparency semantics.
|
||||
|
||||
Making RGBA8888 the normal packer-side representation aligns Studio with
|
||||
runtime `DEC-0029` without introducing a parallel RGB565 compatibility path.
|
||||
This keeps the asset pipeline direct: authoring input is normalized once at the
|
||||
packer boundary, and emitted runtime artifacts carry the runtime color contract.
|
||||
|
||||
Treating index `0` as ordinary is required because transparency now belongs to
|
||||
the alpha channel. This also gives each glyph palette the full 16 authored
|
||||
entries available from the indexed payload.
|
||||
|
||||
Removing magenta color-key behavior avoids a second transparency mechanism that
|
||||
would conflict with meaningful alpha. Allowing partial alpha preserves source
|
||||
image information and supports the broader RGBA8888 renderer direction.
|
||||
|
||||
Keeping fixed 64-palette serialization separates mandatory color-format
|
||||
migration from later payload-size optimization. Palette count and sparse/dense
|
||||
serialization remain independent questions.
|
||||
|
||||
## Technical Specification
|
||||
|
||||
### Runtime-Facing Glyph Payload
|
||||
|
||||
- The format name remains `GLYPH/indexed_v1`.
|
||||
- The indexed pixel plane remains packed `u4`.
|
||||
- The emitted glyph-bank sheet shape remains unchanged for this wave.
|
||||
- The palette block changes from 2-byte RGB565 entries to 4-byte RGBA8888
|
||||
entries.
|
||||
- The fixed palette block remains `64 * 16` entries for this wave.
|
||||
- The byte-size formula for the palette block MUST be updated from
|
||||
`64 * 16 * 2` to `64 * 16 * 4`.
|
||||
- Size and decoded-size formulas for glyph-bank payloads MUST be updated
|
||||
accordingly.
|
||||
- Palette bytes MUST be interpreted as runtime RGBA channel order.
|
||||
|
||||
### Authored and Projected Palette Data
|
||||
|
||||
- Canonical palette data exposed by Studio/packer SHALL use `rgba8888`.
|
||||
- `convertedRgb565` SHALL be removed from normal authored pipeline metadata,
|
||||
details projections, read projections, fixtures, and tests.
|
||||
- `originalArgb8888` SHALL NOT be the canonical field for runtime-facing or
|
||||
packer-projected palette data in this wave.
|
||||
- ARGB values read from Java image APIs MUST be converted to RGBA8888 before
|
||||
entering canonical packer metadata or emitted payloads.
|
||||
|
||||
### Transparency and Alpha
|
||||
|
||||
- Palette index `0` is ordinary.
|
||||
- A glyph palette has 16 usable entries.
|
||||
- Alpha `0`, alpha between `1` and `254`, and alpha `255` are all valid
|
||||
palette-entry states.
|
||||
- Partial alpha MUST be preserved as data.
|
||||
- Packer warnings or validations that describe partial alpha as flattened,
|
||||
ignored, or unsupported MUST be removed.
|
||||
- Color limit validation MUST be rewritten for 16 usable palette entries.
|
||||
|
||||
### Removed Behaviors
|
||||
|
||||
- RGB565 palette emission is not a normal packer output.
|
||||
- RGB565 palette projection is not a normal Studio/packer read or details
|
||||
contract.
|
||||
- Magenta color-key transparency is not supported in the normal path.
|
||||
- Palette index `0` is not reserved for transparency.
|
||||
- Runtime asset compatibility with stale RGB565 palette payloads is not a
|
||||
Studio/packer requirement for this wave.
|
||||
|
||||
## Propagation Targets
|
||||
|
||||
### Specs
|
||||
|
||||
- `docs/specs/packer/3. Asset Declaration and Virtual Asset Contract Specification.md`
|
||||
- `docs/specs/packer/4. Build Artifacts and Deterministic Packing Specification.md`
|
||||
- `docs/specs/studio/4. Assets Workspace Specification.md`, if Studio-facing
|
||||
palette read/projection behavior is documented there.
|
||||
|
||||
### Code
|
||||
|
||||
- `prometeu-packer` palette model types.
|
||||
- `PackerGlyphBankWalker`.
|
||||
- `PackerAssetWalker` glyph-bank size and metadata calculations.
|
||||
- `PackerAssetDetailsService`.
|
||||
- `PackerReadMessageMapper`.
|
||||
- Any packer serializer/materializer that emits `assets.pa` glyph payloads.
|
||||
- Studio asset workspace read/detail surfaces that display or depend on
|
||||
palette metadata.
|
||||
|
||||
### Tests and Fixtures
|
||||
|
||||
- Packer glyph-bank walker tests.
|
||||
- Asset declaration parser tests that mention `convertedRgb565`.
|
||||
- Runtime asset materializer tests.
|
||||
- File-system workspace service tests.
|
||||
- Fixture `asset.json` files that still declare RGB565-derived palette data.
|
||||
- Test expectations for alpha, 16 usable colors, and removed magenta color-key
|
||||
behavior.
|
||||
|
||||
`main` and `fragments` project assets are expected to be regenerated manually
|
||||
later and are not blockers for this decision.
|
||||
|
||||
## Constraints
|
||||
|
||||
- This decision MUST NOT introduce an RGB565 compatibility mode.
|
||||
- This decision MUST NOT rename the runtime-facing format away from
|
||||
`GLYPH/indexed_v1`.
|
||||
- This decision MUST NOT change scene `palette_id` semantics except as needed
|
||||
to ensure referenced glyph palettes now resolve RGBA8888 entries.
|
||||
- This decision MUST NOT decide variable palette-count serialization.
|
||||
- This decision MUST NOT close or revise `DSC-0005`; that discussion remains a
|
||||
later palette-count optimization track.
|
||||
- Any implementation plan derived from this decision MUST preserve all
|
||||
normative requirements above unless the user explicitly requests a decision
|
||||
revision.
|
||||
|
||||
## References
|
||||
|
||||
- Agenda: `AGD-0041`
|
||||
- Runtime decision: `../runtime/discussion/workflow/decisions/DEC-0029-rgba8888-runtime-pixel-format-contract.md`
|
||||
- Related future discussion: `DSC-0005`
|
||||
|
||||
## Revision Log
|
||||
|
||||
- 2026-05-23: Initial accepted decision from `AGD-0041`.
|
||||
@ -0,0 +1,136 @@
|
||||
---
|
||||
id: PLN-0081
|
||||
ticket: studio-packer-rgba8888-asset-pipeline
|
||||
title: RGBA8888 Packer and Studio Spec Propagation
|
||||
status: done
|
||||
created: 2026-05-23
|
||||
completed: 2026-05-23
|
||||
ref_decisions:
|
||||
- DEC-0037
|
||||
tags:
|
||||
- specs
|
||||
- packer
|
||||
- studio
|
||||
- rgba8888
|
||||
---
|
||||
|
||||
## Objective
|
||||
|
||||
Propagate `DEC-0037` into the normative Studio and packer specifications before
|
||||
code implementation starts.
|
||||
|
||||
## Background
|
||||
|
||||
`DEC-0037` keeps `GLYPH/indexed_v1` but changes its palette contract from
|
||||
RGB565 to RGBA8888. Specs currently still describe RGB565 palette bytes,
|
||||
`convertedRgb565`, and a 2048-byte palette block.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
|
||||
- Update packer specs for `asset.json` palette metadata and `assets.pa`
|
||||
glyph-bank payload layout.
|
||||
- Update Studio asset workspace specs if they expose palette metadata or
|
||||
details/read projection behavior.
|
||||
- Preserve the fixed 64-palette model and explicitly leave `DSC-0005` for a
|
||||
later palette-count optimization.
|
||||
|
||||
### Excluded
|
||||
|
||||
- Code changes.
|
||||
- Test changes.
|
||||
- Variable palette-count serialization.
|
||||
- Runtime spec changes, except references to runtime `DEC-0029`.
|
||||
|
||||
## Execution Steps
|
||||
|
||||
### Step 1 - Update asset declaration palette fields
|
||||
|
||||
**What:** Replace `originalArgb8888`/`convertedRgb565` examples and rules with
|
||||
canonical `rgba8888`.
|
||||
|
||||
**How:** Edit the palette portions of the asset declaration spec so
|
||||
`output.pipeline.palettes[*].palette.rgba8888` is the canonical field. State
|
||||
that ARGB may be an input API detail but is not canonical authored/projected
|
||||
palette data.
|
||||
|
||||
**File(s):**
|
||||
|
||||
- `docs/specs/packer/3. Asset Declaration and Virtual Asset Contract Specification.md`
|
||||
|
||||
### Step 2 - Update glyph-bank payload layout
|
||||
|
||||
**What:** Change `GLYPH/indexed_v1` palette layout from RGB565 to RGBA8888.
|
||||
|
||||
**How:** Keep the format name. Change the palette block from
|
||||
`64 * 16 * 2 = 2048` bytes to `64 * 16 * 4 = 4096` bytes. Update size and
|
||||
decoded-size formulas. State that palette entries use runtime RGBA channel
|
||||
order.
|
||||
|
||||
**File(s):**
|
||||
|
||||
- `docs/specs/packer/4. Build Artifacts and Deterministic Packing Specification.md`
|
||||
|
||||
### Step 3 - Document alpha and index semantics
|
||||
|
||||
**What:** Document that palette index `0` is ordinary and all 16 palette entries
|
||||
are usable.
|
||||
|
||||
**How:** Add normative rules that transparency and partial transparency are
|
||||
encoded only by RGBA alpha, magenta color-key is removed, and partial alpha is
|
||||
valid palette data.
|
||||
|
||||
**File(s):**
|
||||
|
||||
- `docs/specs/packer/3. Asset Declaration and Virtual Asset Contract Specification.md`
|
||||
- `docs/specs/packer/4. Build Artifacts and Deterministic Packing Specification.md`
|
||||
|
||||
### Step 4 - Update Studio-facing projection rules
|
||||
|
||||
**What:** Ensure Studio-facing docs use `rgba8888` where palette metadata is
|
||||
visible.
|
||||
|
||||
**How:** Update asset workspace language only where it describes palette read,
|
||||
details, scene preview, or palette default behavior. Keep scene `palette_id`
|
||||
semantics unchanged.
|
||||
|
||||
**File(s):**
|
||||
|
||||
- `docs/specs/studio/4. Assets Workspace Specification.md`
|
||||
|
||||
## Test Requirements
|
||||
|
||||
### Unit Tests
|
||||
|
||||
- Not applicable for this spec-only plan.
|
||||
|
||||
### Integration Tests
|
||||
|
||||
- Not applicable for this spec-only plan.
|
||||
|
||||
### Manual Verification
|
||||
|
||||
- Search specs for stale `RGB565`, `convertedRgb565`, 2048-byte palette block,
|
||||
reserved index `0`, and magenta color-key language.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [x] Packer specs define `rgba8888` as the canonical palette field.
|
||||
- [x] `GLYPH/indexed_v1` remains named exactly that.
|
||||
- [x] Palette block formulas use `64 * 16 * 4 = 4096`.
|
||||
- [x] Specs state palette index `0` is ordinary and all 16 entries are usable.
|
||||
- [x] Specs state partial alpha is valid and magenta color-key is removed.
|
||||
- [x] Specs leave `DSC-0005` variable palette-count work out of scope.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Depends on accepted `DEC-0037`.
|
||||
- Should complete before code plans are implemented.
|
||||
|
||||
## Risks
|
||||
|
||||
- Leaving stale RGB565 examples in specs will make implementation review
|
||||
ambiguous.
|
||||
- Over-editing scene specs could accidentally imply a scene payload change that
|
||||
`DEC-0037` does not authorize.
|
||||
@ -0,0 +1,144 @@
|
||||
---
|
||||
id: PLN-0082
|
||||
ticket: studio-packer-rgba8888-asset-pipeline
|
||||
title: RGBA8888 Palette Model and ARGB Conversion
|
||||
status: done
|
||||
created: 2026-05-23
|
||||
completed: 2026-05-23
|
||||
ref_decisions:
|
||||
- DEC-0037
|
||||
tags:
|
||||
- packer
|
||||
- palette
|
||||
- rgba8888
|
||||
- conversion
|
||||
---
|
||||
|
||||
## Objective
|
||||
|
||||
Replace the packer palette model with canonical RGBA8888 data and introduce a
|
||||
single deliberate ARGB-to-RGBA conversion boundary for image inputs.
|
||||
|
||||
## Background
|
||||
|
||||
`PackerPaletteV1` currently stores `originalArgb8888` and `convertedRgb565`.
|
||||
`PackerGlyphBankWalker` reads Java ARGB pixels, drops alpha semantics, reserves
|
||||
index `0`, applies magenta color-key, and converts colors to RGB565.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
|
||||
- Replace `PackerPaletteV1` fields with canonical `rgba8888`.
|
||||
- Add or localize ARGB-to-RGBA conversion helpers.
|
||||
- Preserve partial alpha.
|
||||
- Remove RGB565 conversion from the normal palette model.
|
||||
- Expand glyph palette extraction from 15 to 16 usable colors.
|
||||
|
||||
### Excluded
|
||||
|
||||
- Binary `assets.pa` emission changes.
|
||||
- Read/detail API projection changes outside direct model compile fixes.
|
||||
- Spec edits.
|
||||
- Fixture regeneration for `main` and `fragments`.
|
||||
|
||||
## Execution Steps
|
||||
|
||||
### Step 1 - Replace the palette record
|
||||
|
||||
**What:** Change the packer palette model to carry canonical RGBA8888 values.
|
||||
|
||||
**How:** Replace `int[] originalArgb8888, short[] convertedRgb565` with
|
||||
`int[] rgba8888`. Keep the record name only if it remains semantically useful;
|
||||
otherwise rename only if all callers are updated in the same change.
|
||||
|
||||
**File(s):**
|
||||
|
||||
- `prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerPaletteV1.java`
|
||||
|
||||
### Step 2 - Convert ARGB image pixels to RGBA
|
||||
|
||||
**What:** Introduce one conversion path from Java `BufferedImage` ARGB to raw
|
||||
runtime RGBA.
|
||||
|
||||
**How:** Convert `0xAARRGGBB` to `0xRRGGBBAA` when building palette entries.
|
||||
Do not store ARGB in canonical packer metadata.
|
||||
|
||||
**File(s):**
|
||||
|
||||
- `prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerGlyphBankWalker.java`
|
||||
|
||||
### Step 3 - Remove magenta color-key and reserved index behavior
|
||||
|
||||
**What:** Stop treating alpha zero or magenta as special palette-index behavior.
|
||||
|
||||
**How:** Remove `COLOR_KEY_RGB`. Build palette identity from full RGBA8888
|
||||
values, including alpha. Assign indices from `0` through `15`.
|
||||
|
||||
**File(s):**
|
||||
|
||||
- `prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerGlyphBankWalker.java`
|
||||
|
||||
### Step 4 - Preserve partial alpha
|
||||
|
||||
**What:** Remove partial-alpha warnings and flattening.
|
||||
|
||||
**How:** Delete the `partialAlphaFound` warning path. Ensure pixels with alpha
|
||||
from `0` through `255` participate in palette extraction by full RGBA value.
|
||||
|
||||
**File(s):**
|
||||
|
||||
- `prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerGlyphBankWalker.java`
|
||||
|
||||
### Step 5 - Update color-limit validation
|
||||
|
||||
**What:** Change validation from 15 usable colors to 16 usable colors.
|
||||
|
||||
**How:** Replace `MAX_COLORS_PER_PALETTE = 15` with a 16-entry limit and update
|
||||
diagnostic wording to remove `indices 1..15`.
|
||||
|
||||
**File(s):**
|
||||
|
||||
- `prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerGlyphBankWalker.java`
|
||||
|
||||
## Test Requirements
|
||||
|
||||
### Unit Tests
|
||||
|
||||
- Update `PackerGlyphBankWalkerTest` to assert:
|
||||
- 16 unique RGBA colors are valid.
|
||||
- a 17th unique RGBA color is blocking.
|
||||
- alpha `0`, partial alpha, and alpha `255` are preserved.
|
||||
- magenta is treated as an ordinary color.
|
||||
- palette index `0` can hold an ordinary color.
|
||||
|
||||
### Integration Tests
|
||||
|
||||
- None for this plan; binary emission is covered by a later plan.
|
||||
|
||||
### Manual Verification
|
||||
|
||||
- Search packer model and glyph walker sources for `convertedRgb565`,
|
||||
`originalArgb8888`, `COLOR_KEY_RGB`, `convertToRgb565`, and partial-alpha
|
||||
warning text.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [x] `PackerPaletteV1` exposes `rgba8888` and no RGB565 field.
|
||||
- [x] `PackerGlyphBankWalker` stores canonical RGBA values.
|
||||
- [x] Magenta is not special.
|
||||
- [x] Alpha zero and partial alpha are valid palette data.
|
||||
- [x] All 16 palette indices are usable.
|
||||
- [x] Unit tests cover conversion and palette indexing semantics.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Depends on `PLN-0081` for spec wording.
|
||||
- Must complete before payload emission and projection plans.
|
||||
|
||||
## Risks
|
||||
|
||||
- Confusing Java ARGB input with runtime RGBA output will create hard-to-debug
|
||||
byte-order failures.
|
||||
- Cache reuse may keep stale palette metadata if reusable metadata checks are
|
||||
not updated with the model change.
|
||||
@ -0,0 +1,145 @@
|
||||
---
|
||||
id: PLN-0083
|
||||
ticket: studio-packer-rgba8888-asset-pipeline
|
||||
title: RGBA8888 Glyph Payload Emission and Size Accounting
|
||||
status: done
|
||||
created: 2026-05-23
|
||||
completed: 2026-05-23
|
||||
ref_decisions:
|
||||
- DEC-0037
|
||||
tags:
|
||||
- packer
|
||||
- assets-pa
|
||||
- glyph-bank
|
||||
- rgba8888
|
||||
---
|
||||
|
||||
## Objective
|
||||
|
||||
Change packer glyph-bank runtime payload emission from RGB565 palette bytes to
|
||||
RGBA8888 palette bytes while keeping `GLYPH/indexed_v1` and the fixed
|
||||
64-palette model.
|
||||
|
||||
## Background
|
||||
|
||||
`DEC-0037` requires `GLYPH/indexed_v1` to retain its format name and indexed
|
||||
model but emit RGBA8888 palette entries. Current payload emission in
|
||||
`FileSystemPackerWorkspaceService` writes RGB565 bytes, reserves index `0`, and
|
||||
uses color-key bytes.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
|
||||
- Update glyph-bank payload palette byte emission.
|
||||
- Update measured payload size and decoded-size accounting.
|
||||
- Remove RGB565 palette constants and palette color-key bytes from emission.
|
||||
- Keep scene `palette_id` payload semantics unchanged.
|
||||
|
||||
### Excluded
|
||||
|
||||
- Palette model extraction changes covered by `PLN-0082`.
|
||||
- Read/detail projection changes covered by `PLN-0084`.
|
||||
- Variable palette count.
|
||||
|
||||
## Execution Steps
|
||||
|
||||
### Step 1 - Update glyph payload constants and formulas
|
||||
|
||||
**What:** Change palette entry width from 2 bytes to 4 bytes.
|
||||
|
||||
**How:** Introduce clear constants for palette count, colors per palette, and
|
||||
RGBA bytes per color. Use `64 * 16 * 4` for palette block size.
|
||||
|
||||
**File(s):**
|
||||
|
||||
- `prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/FileSystemPackerWorkspaceService.java`
|
||||
- `prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerAssetWalker.java`
|
||||
- `prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerRuntimeAssetMaterializer.java`
|
||||
|
||||
### Step 2 - Emit RGBA8888 palette bytes
|
||||
|
||||
**What:** Write each palette color as runtime RGBA channel bytes.
|
||||
|
||||
**How:** Replace RGB565 little-endian writes with four byte writes in `R`, `G`,
|
||||
`B`, `A` order derived from the canonical `rgba8888` integer value.
|
||||
|
||||
**File(s):**
|
||||
|
||||
- `prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/FileSystemPackerWorkspaceService.java`
|
||||
|
||||
### Step 3 - Remove reserved color-key emission
|
||||
|
||||
**What:** Stop writing a synthetic color-key value at palette index `0`.
|
||||
|
||||
**How:** Remove `GLYPH_BANK_COLOR_KEY_RGB565` and any emission path that fills
|
||||
index `0` with magenta. Emit declared RGBA values for index `0` like every
|
||||
other index.
|
||||
|
||||
**File(s):**
|
||||
|
||||
- `prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/FileSystemPackerWorkspaceService.java`
|
||||
|
||||
### Step 4 - Update declared palette lookup
|
||||
|
||||
**What:** Read `rgba8888` from declared pipeline metadata.
|
||||
|
||||
**How:** Replace `originalArgb8888`/`convertedRgb565` lookup paths with
|
||||
`rgba8888`. Ensure empty or malformed `rgba8888` is structural failure where
|
||||
emission requires a palette.
|
||||
|
||||
**File(s):**
|
||||
|
||||
- `prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/FileSystemPackerWorkspaceService.java`
|
||||
- `prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerAssetWalker.java`
|
||||
|
||||
### Step 5 - Keep scene payload stable
|
||||
|
||||
**What:** Preserve scene `palette_id` layout.
|
||||
|
||||
**How:** Do not change `writeSceneBankPayload` except compile fixes caused by
|
||||
shared helper changes. Scene tile records continue carrying `palette_id` as an
|
||||
index into the referenced glyph palette.
|
||||
|
||||
**File(s):**
|
||||
|
||||
- `prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/FileSystemPackerWorkspaceService.java`
|
||||
|
||||
## Test Requirements
|
||||
|
||||
### Unit Tests
|
||||
|
||||
- Add or update tests asserting glyph payload size includes a 4096-byte palette
|
||||
block.
|
||||
- Assert emitted palette bytes for a known RGBA value, including alpha.
|
||||
- Assert index `0` emits declared RGBA data, not magenta.
|
||||
|
||||
### Integration Tests
|
||||
|
||||
- Update workspace build tests that read `build/assets.pa` and assert glyph-bank
|
||||
offsets/sizes.
|
||||
|
||||
### Manual Verification
|
||||
|
||||
- Search production packer code for `GLYPH_BANK_COLOR_KEY_RGB565`,
|
||||
`convertedRgb565`, RGB565 palette byte writes, and hard-coded `2048`.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [x] Glyph payloads keep `GLYPH/indexed_v1`.
|
||||
- [x] Glyph palette blocks are 4096 bytes.
|
||||
- [x] Palette entries are emitted as RGBA bytes in runtime channel order.
|
||||
- [x] No synthetic magenta/color-key value is emitted.
|
||||
- [x] Size and decoded-size metadata match the new payload length.
|
||||
- [x] Scene `palette_id` payload shape is unchanged.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Depends on `PLN-0082`.
|
||||
- Should run before tests/fixture conformance in `PLN-0085`.
|
||||
|
||||
## Risks
|
||||
|
||||
- Updating size formulas without updating emitted bytes, or vice versa, will
|
||||
corrupt payload slicing in `assets.pa`.
|
||||
- Accidental scene payload edits could widen the change beyond `DEC-0037`.
|
||||
@ -0,0 +1,141 @@
|
||||
---
|
||||
id: PLN-0084
|
||||
ticket: studio-packer-rgba8888-asset-pipeline
|
||||
title: RGBA8888 Packer Projections and Workspace APIs
|
||||
status: done
|
||||
created: 2026-05-23
|
||||
completed: 2026-05-23
|
||||
ref_decisions:
|
||||
- DEC-0037
|
||||
tags:
|
||||
- packer
|
||||
- studio
|
||||
- api
|
||||
- rgba8888
|
||||
---
|
||||
|
||||
## Objective
|
||||
|
||||
Update packer and Studio-facing read/detail/write projections so palette data
|
||||
is exposed and accepted as `rgba8888`, with no normal `convertedRgb565` or
|
||||
canonical `originalArgb8888` surface.
|
||||
|
||||
## Background
|
||||
|
||||
`DEC-0037` requires packer metadata, Studio read/detail projections, tests, and
|
||||
fixtures to stop treating RGB565 as normal palette data. Several services still
|
||||
parse or project `originalArgb8888` and `convertedRgb565`.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
|
||||
- Update parser and declaration normalization for palette metadata.
|
||||
- Update read/detail mappers.
|
||||
- Update palette-overhauling and bank-composition support to emit `rgba8888`.
|
||||
- Preserve existing DTO shapes except for palette payload field names.
|
||||
|
||||
### Excluded
|
||||
|
||||
- Binary payload byte emission.
|
||||
- Renderer/runtime changes.
|
||||
- Manual regeneration of `main` and `fragments`.
|
||||
|
||||
## Execution Steps
|
||||
|
||||
### Step 1 - Update asset declaration parsing
|
||||
|
||||
**What:** Parse canonical `rgba8888` arrays from `asset.json`.
|
||||
|
||||
**How:** Replace palette validation that requires `originalArgb8888` and
|
||||
`convertedRgb565` with validation for `rgba8888`. Keep numeric validation.
|
||||
Reject malformed or missing `rgba8888` where a palette payload is required.
|
||||
|
||||
**File(s):**
|
||||
|
||||
- `prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerAssetDeclarationParser.java`
|
||||
|
||||
### Step 2 - Update details projection
|
||||
|
||||
**What:** Return `rgba8888` from asset details.
|
||||
|
||||
**How:** Change `palettePayloadFromNode` to project `{ index, rgba8888 }`.
|
||||
Remove `convertedRgb565` and canonical `originalArgb8888` from details output.
|
||||
|
||||
**File(s):**
|
||||
|
||||
- `prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerAssetDetailsService.java`
|
||||
|
||||
### Step 3 - Update read message mapping
|
||||
|
||||
**What:** Map `PackerPaletteV1` and cached palette metadata to `rgba8888`.
|
||||
|
||||
**How:** Replace `originalArgb8888` and `convertedRgb565` mapping with a single
|
||||
`rgba8888` array.
|
||||
|
||||
**File(s):**
|
||||
|
||||
- `prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerReadMessageMapper.java`
|
||||
|
||||
### Step 4 - Update workspace write operations
|
||||
|
||||
**What:** Ensure packer write APIs that apply palette changes persist `rgba8888`.
|
||||
|
||||
**How:** Replace palette-overhauling and related write-lane logic that requires
|
||||
both `originalArgb8888` and `convertedRgb565` with a `rgba8888` contract.
|
||||
|
||||
**File(s):**
|
||||
|
||||
- `prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/FileSystemPackerWorkspaceService.java`
|
||||
|
||||
### Step 5 - Update cache reuse and walk projection checks
|
||||
|
||||
**What:** Make cache reuse validate new palette metadata.
|
||||
|
||||
**How:** Replace reusable metadata checks for `originalArgb8888` with
|
||||
`rgba8888`. Stale cached RGB565-derived metadata must not be considered
|
||||
reusable.
|
||||
|
||||
**File(s):**
|
||||
|
||||
- `prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerGlyphBankWalker.java`
|
||||
- `prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerAssetWalker.java`
|
||||
|
||||
## Test Requirements
|
||||
|
||||
### Unit Tests
|
||||
|
||||
- Update parser tests to accept `rgba8888` and reject missing/malformed
|
||||
`rgba8888`.
|
||||
- Update details service tests to assert `rgba8888`.
|
||||
- Update read mapper coverage for `PackerPaletteV1`.
|
||||
- Update write operation tests for palette overhauling.
|
||||
|
||||
### Integration Tests
|
||||
|
||||
- Exercise a workspace read/build flow from `asset.json` using `rgba8888`.
|
||||
|
||||
### Manual Verification
|
||||
|
||||
- Search `prometeu-packer` for `originalArgb8888` and `convertedRgb565` after
|
||||
this plan. Remaining matches must be migration-only or removed in later
|
||||
fixture plan.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [x] `asset.json` palette metadata uses `rgba8888`.
|
||||
- [x] Details/read projections expose `rgba8888`.
|
||||
- [x] Workspace write operations persist `rgba8888`.
|
||||
- [x] Stale RGB565-derived cache metadata is not reused as canonical.
|
||||
- [x] No normal API requires `convertedRgb565`.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Depends on `PLN-0082`.
|
||||
- Can run partly in parallel with `PLN-0083` after the palette model is stable.
|
||||
|
||||
## Risks
|
||||
|
||||
- DTO consumers may assume old map keys. Tests must cover the Studio-facing
|
||||
projection boundary.
|
||||
- Cache reuse can hide stale behavior if not invalidated by metadata shape.
|
||||
@ -0,0 +1,141 @@
|
||||
---
|
||||
id: PLN-0085
|
||||
ticket: studio-packer-rgba8888-asset-pipeline
|
||||
title: RGBA8888 Tests, Fixtures, and Conformance Updates
|
||||
status: done
|
||||
created: 2026-05-23
|
||||
completed: 2026-05-23
|
||||
ref_decisions:
|
||||
- DEC-0037
|
||||
tags:
|
||||
- tests
|
||||
- fixtures
|
||||
- conformance
|
||||
- rgba8888
|
||||
---
|
||||
|
||||
## Objective
|
||||
|
||||
Update packer tests and fixtures so RGBA8888 is the asserted source of truth and
|
||||
RGB565-specific expectations are removed.
|
||||
|
||||
## Background
|
||||
|
||||
`DEC-0037` requires tests and fixtures to stop proving RGB565 behavior. The code
|
||||
base has parser, walker, details, materializer, and workspace service tests that
|
||||
mention `convertedRgb565`, `originalArgb8888`, 15-color limits, magenta
|
||||
color-key behavior, and RGB565 payload sizes.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
|
||||
- Update packer unit tests and integration-style workspace tests.
|
||||
- Update fixture `asset.json` files under packer test resources.
|
||||
- Add conformance-oriented assertions for RGBA channel order, alpha, index `0`,
|
||||
16 usable colors, and removed color-key behavior.
|
||||
|
||||
### Excluded
|
||||
|
||||
- Manual regeneration of `test-projects/main` and `fragments`.
|
||||
- Production code changes except minor compile-fix test helpers.
|
||||
- Runtime repository tests.
|
||||
|
||||
## Execution Steps
|
||||
|
||||
### Step 1 - Update parser and details tests
|
||||
|
||||
**What:** Replace palette payload fixtures with `rgba8888`.
|
||||
|
||||
**How:** Rewrite test JSON snippets and assertions that mention
|
||||
`originalArgb8888` or `convertedRgb565`.
|
||||
|
||||
**File(s):**
|
||||
|
||||
- `prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/PackerAssetDeclarationParserTest.java`
|
||||
- `prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/PackerAssetDetailsServiceTest.java`
|
||||
|
||||
### Step 2 - Update glyph walker tests
|
||||
|
||||
**What:** Assert new palette extraction semantics.
|
||||
|
||||
**How:** Add image fixtures in tests that cover:
|
||||
16 colors, 17-color failure, alpha 0, partial alpha, magenta as ordinary color,
|
||||
and index `0` as ordinary.
|
||||
|
||||
**File(s):**
|
||||
|
||||
- `prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/repositories/PackerGlyphBankWalkerTest.java`
|
||||
|
||||
### Step 3 - Update materializer and workspace service tests
|
||||
|
||||
**What:** Assert RGBA8888 runtime payload and metadata behavior.
|
||||
|
||||
**How:** Update tests that read `assets.pa`, measured bank size, payload offsets,
|
||||
and palette byte expectations. Replace RGB565 byte checks with RGBA byte checks.
|
||||
|
||||
**File(s):**
|
||||
|
||||
- `prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/repositories/PackerRuntimeAssetMaterializerTest.java`
|
||||
- `prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/FileSystemPackerWorkspaceServiceTest.java`
|
||||
- `prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/PackerRuntimePatchServiceTest.java`
|
||||
- `prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/PackerRuntimeRegistryTest.java`
|
||||
|
||||
### Step 4 - Update packer fixture assets
|
||||
|
||||
**What:** Replace fixture `asset.json` palette fields with `rgba8888`.
|
||||
|
||||
**How:** Edit packer test fixtures under `src/test/resources/fixtures` so
|
||||
canonical palette declarations do not contain `convertedRgb565`.
|
||||
|
||||
**File(s):**
|
||||
|
||||
- `prometeu-packer/prometeu-packer-v1/src/test/resources/fixtures/**/asset.json`
|
||||
|
||||
### Step 5 - Add stale-contract guards
|
||||
|
||||
**What:** Add tests or quality checks that catch old RGB565 palette contract
|
||||
residue.
|
||||
|
||||
**How:** Add assertions in existing tests or a focused repository quality test
|
||||
that normal packer specs/tests/fixtures do not contain `convertedRgb565` or
|
||||
RGB565 glyph palette payload expectations.
|
||||
|
||||
**File(s):**
|
||||
|
||||
- Existing packer test suites under `prometeu-packer/prometeu-packer-v1/src/test/java`
|
||||
|
||||
## Test Requirements
|
||||
|
||||
### Unit Tests
|
||||
|
||||
- Parser, details, mapper, glyph walker, and workspace service unit tests must
|
||||
all pass.
|
||||
|
||||
### Integration Tests
|
||||
|
||||
- Run the relevant Gradle test task for `prometeu-packer-v1`.
|
||||
|
||||
### Manual Verification
|
||||
|
||||
- Search for `convertedRgb565`, `originalArgb8888`, `RGB565`, `rgb565`, `2048`,
|
||||
and magenta color-key references in packer tests and fixtures.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [x] Tests use `rgba8888` as canonical palette data.
|
||||
- [x] Tests assert RGBA channel order and alpha preservation.
|
||||
- [x] Tests assert 16 usable palette entries.
|
||||
- [x] Tests assert magenta is ordinary or no longer special.
|
||||
- [x] Tests assert 4096-byte palette blocks where payload size is checked.
|
||||
- [x] Packer test fixtures no longer rely on `convertedRgb565`.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Depends on `PLN-0082`, `PLN-0083`, and `PLN-0084`.
|
||||
|
||||
## Risks
|
||||
|
||||
- Some stale generated fixture data may be easier to regenerate than hand-edit.
|
||||
- Overbroad residue checks could flag historical discussion artifacts; scope
|
||||
checks to normal specs, code, tests, and fixtures.
|
||||
@ -0,0 +1,159 @@
|
||||
---
|
||||
id: PLN-0086
|
||||
ticket: studio-packer-rgba8888-asset-pipeline
|
||||
title: RGBA8888 Final Integration and Residue Removal
|
||||
status: done
|
||||
created: 2026-05-23
|
||||
completed: 2026-05-23
|
||||
ref_decisions:
|
||||
- DEC-0037
|
||||
tags:
|
||||
- integration
|
||||
- validation
|
||||
- residue-removal
|
||||
- rgba8888
|
||||
---
|
||||
|
||||
## Objective
|
||||
|
||||
Run final integration checks and remove normal-path RGB565 residue after the
|
||||
spec, model, emission, projection, and test plans are implemented.
|
||||
|
||||
## Background
|
||||
|
||||
`DEC-0037` prohibits RGB565 compatibility mode, normal `convertedRgb565`
|
||||
projection, magenta color-key behavior, reserved palette index `0`, and partial
|
||||
alpha flattening. Final validation must prove that the repository implements
|
||||
those constraints coherently.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
|
||||
- Repository-wide residue audit.
|
||||
- Full relevant Gradle test execution.
|
||||
- Discussion workflow validation.
|
||||
- Final implementation notes for follow-up plan or lesson creation.
|
||||
|
||||
### Excluded
|
||||
|
||||
- New feature design.
|
||||
- Runtime repository changes.
|
||||
- Variable palette-count work in `DSC-0005`.
|
||||
- Manual regeneration of `main` and `fragments`.
|
||||
|
||||
## Execution Steps
|
||||
|
||||
### Step 1 - Run focused residue searches
|
||||
|
||||
**What:** Find stale RGB565 palette-contract residue.
|
||||
|
||||
**How:** Search production specs/code/tests/fixtures for:
|
||||
`convertedRgb565`, `originalArgb8888`, `RGB565`, `rgb565`, `COLOR_KEY`,
|
||||
`magenta`, `2048`, `indices 1..15`, and partial-alpha flattening text.
|
||||
Classify each match as removed, historical discussion-only, or out-of-scope.
|
||||
|
||||
**File(s):**
|
||||
|
||||
- `docs/specs/packer/**`
|
||||
- `docs/specs/studio/**`
|
||||
- `prometeu-packer/**`
|
||||
|
||||
### Step 2 - Run packer tests
|
||||
|
||||
**What:** Execute the packer test suite.
|
||||
|
||||
**How:** Run the Gradle test task that covers `prometeu-packer-v1` and related
|
||||
API modules.
|
||||
|
||||
**File(s):**
|
||||
|
||||
- Gradle modules under `prometeu-packer`
|
||||
|
||||
### Step 3 - Run broader build validation
|
||||
|
||||
**What:** Verify Studio build integration still compiles.
|
||||
|
||||
**How:** Run the repository's standard build or the narrowest existing Gradle
|
||||
task that compiles Studio plus packer consumers.
|
||||
|
||||
**File(s):**
|
||||
|
||||
- Root Gradle project
|
||||
- `prometeu-app`
|
||||
- `prometeu-packer`
|
||||
|
||||
### Step 4 - Validate discussion workflow
|
||||
|
||||
**What:** Ensure discussion artifacts remain consistent.
|
||||
|
||||
**How:** Run `discussion validate` after implementation and before any
|
||||
housekeeping.
|
||||
|
||||
**File(s):**
|
||||
|
||||
- `discussion/index.ndjson`
|
||||
- `discussion/workflow/**`
|
||||
|
||||
### Step 5 - Record remaining intentional residue
|
||||
|
||||
**What:** Document any allowed remaining RGB565 references.
|
||||
|
||||
**How:** If matches remain only in historical discussions, runtime references,
|
||||
or explicit future migration notes, list them in the implementation summary so
|
||||
housekeeping and lesson work can distinguish intentional context from missed
|
||||
normal-path behavior.
|
||||
|
||||
**File(s):**
|
||||
|
||||
- Implementation summary or future lesson, after code work is complete.
|
||||
|
||||
## Test Requirements
|
||||
|
||||
### Unit Tests
|
||||
|
||||
- Packer unit tests must pass.
|
||||
|
||||
### Integration Tests
|
||||
|
||||
- Packer workspace build/materialization tests must pass.
|
||||
- Studio compile/build validation must pass.
|
||||
|
||||
### Manual Verification
|
||||
|
||||
- Residue search results must contain no normal-path `convertedRgb565`,
|
||||
RGB565 glyph palette payload, magenta color-key, reserved index `0`, or
|
||||
partial-alpha flattening behavior.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [x] `discussion validate` passes.
|
||||
- [x] Packer tests pass.
|
||||
- [x] Studio/packer build validation passes.
|
||||
- [x] Normal specs/code/tests/fixtures contain no stale RGB565 glyph-palette
|
||||
contract residue.
|
||||
- [x] Remaining stale-looking matches, if any, are documented as historical or
|
||||
explicitly out of scope.
|
||||
- [x] `DSC-0005` remains untouched except by explicit future work.
|
||||
|
||||
## Execution Notes
|
||||
|
||||
- `discussion validate` passed.
|
||||
- `./gradlew build` passed.
|
||||
- Residue search found only:
|
||||
- normative spec statements explicitly saying `convertedRgb565` and magenta
|
||||
color-key behavior are not normal contract;
|
||||
- `test-projects/main` asset manifests, which are intentionally out of scope
|
||||
for this implementation because they will be regenerated manually later.
|
||||
- `DSC-0005` was not modified.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Depends on `PLN-0081` through `PLN-0085`.
|
||||
|
||||
## Risks
|
||||
|
||||
- A broad residue search may include historical discussions and cause false
|
||||
positives.
|
||||
- Generated project cache files may change during tests; those changes must be
|
||||
reviewed separately from source edits.
|
||||
@ -118,6 +118,9 @@ Rules:
|
||||
- collections such as `output.pipeline.palettes` may remain JSON arrays for authoring ergonomics;
|
||||
- when a collection member has runtime- or packing-relevant identity, each entry must carry an explicit `index`;
|
||||
- `output.pipeline.palettes` entries must use the shape `{ "index": <int>, "palette": { ... } }`;
|
||||
- glyph-bank palette payloads must expose canonical `rgba8888` color values in runtime RGBA channel order;
|
||||
- ARGB values produced by host image APIs are import-side implementation details, not canonical declaration or projection fields;
|
||||
- `convertedRgb565` is not part of the normal palette contract;
|
||||
- the packer must sort such entries by numeric `index`, not by physical array position;
|
||||
- duplicate or malformed indices are structural errors;
|
||||
- missing or malformed `palette` payloads are structural errors;
|
||||
@ -133,8 +136,7 @@ Example:
|
||||
{
|
||||
"index": 0,
|
||||
"palette": {
|
||||
"originalArgb8888": [4294901760, 4278255360],
|
||||
"convertedRgb565": [63488, 2016]
|
||||
"rgba8888": [4294901760, 16711935]
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -143,6 +145,19 @@ Example:
|
||||
}
|
||||
```
|
||||
|
||||
For glyph-bank palettes, each `rgba8888` integer is interpreted as
|
||||
`0xRRGGBBAA`.
|
||||
|
||||
Rules:
|
||||
|
||||
- palette index `0` is an ordinary palette slot;
|
||||
- glyph palettes expose 16 usable colors;
|
||||
- transparency and partial transparency are represented only by the alpha byte
|
||||
in the RGBA8888 palette value;
|
||||
- alpha values from `0` through `255` are valid;
|
||||
- magenta color-key behavior is not supported as a normal declaration,
|
||||
projection, or publication rule.
|
||||
|
||||
## Metadata Source Segmentation and Runtime Sink
|
||||
|
||||
`asset.json` may keep metadata segmented by concern during authoring.
|
||||
|
||||
@ -178,10 +178,10 @@ Rules:
|
||||
|
||||
- the serialized payload for `GLYPH/indexed_v1` is:
|
||||
1. one packed `u4` pixel plane for the full emitted sheet;
|
||||
2. one palette block of `64 * 16 * 2 = 2048` bytes;
|
||||
2. one palette block of `64 * 16 * 4 = 4096` bytes;
|
||||
- packed pixel bytes must be emitted by the packer, not inferred later by the runtime;
|
||||
- the palette block is serialized as `RGB565` `u16` values;
|
||||
- palette bytes must be emitted in little-endian order;
|
||||
- the palette block is serialized as RGBA8888 values in runtime RGBA channel order;
|
||||
- each palette entry is emitted as four bytes in `R`, `G`, `B`, `A` order;
|
||||
- payload interpretation must not depend on incidental per-artifact boundaries.
|
||||
|
||||
### Runtime Entry Derivation
|
||||
@ -198,8 +198,8 @@ Rules:
|
||||
- `palette_count`
|
||||
- tile-bank v1 requires `palette_count = 64`;
|
||||
- tile-bank v1 size formulas are:
|
||||
- `size = ceil(width * height / 2) + 2048`
|
||||
- `decoded_size = (width * height) + 2048`
|
||||
- `size = ceil(width * height / 2) + 4096`
|
||||
- `decoded_size = (width * height) + 4096`
|
||||
|
||||
### Palette Contract
|
||||
|
||||
@ -207,8 +207,13 @@ Rules:
|
||||
|
||||
- bank palettes are declared under `output.pipeline.palettes`;
|
||||
- each palette declaration uses the shape `{ "index": <int>, "palette": { ... } }`;
|
||||
- each palette payload uses canonical `rgba8888` entries interpreted as `0xRRGGBBAA`;
|
||||
- palette ordering is ascending numeric `index`, never raw array position;
|
||||
- palette ids in the emitted glyph bank are the normalized declared palette indices;
|
||||
- palette index `0` is ordinary and may carry any RGBA8888 value;
|
||||
- glyph palettes have 16 usable entries;
|
||||
- transparency and partial transparency are encoded only by RGBA alpha;
|
||||
- magenta color-key behavior is not part of the normal glyph-bank contract;
|
||||
- any tile in the bank may be rendered with any palette in the bank at runtime;
|
||||
- palette selection is a runtime draw concern, not a tile-payload embedding concern.
|
||||
|
||||
|
||||
@ -224,6 +224,10 @@ Rules:
|
||||
- `output.format`
|
||||
- `codec`
|
||||
- `preload`
|
||||
- Glyph-bank palette projections must use canonical `rgba8888` values when
|
||||
palette data is shown.
|
||||
- Studio-facing glyph palette views must not expose `convertedRgb565` as normal
|
||||
runtime or tooling truth.
|
||||
- Runtime-facing output information must not require raw JSON inspection.
|
||||
|
||||
### Inputs / Preview
|
||||
@ -328,6 +332,10 @@ Rules:
|
||||
- `glyph_asset_id` is derived from the glyph asset selected for the layer;
|
||||
- `parallax_factor_x` and `parallax_factor_y` are layer-owned editorial fields in Studio;
|
||||
- `palette_id` has a layer-owned default and may be overridden per tile when the supported editorial surface provides an explicit override;
|
||||
- `palette_id` remains an index into the referenced glyph palette and does not
|
||||
imply a color format change in the scene request;
|
||||
- referenced glyph palettes resolve to RGBA8888 entries, with palette index `0`
|
||||
treated as an ordinary palette slot;
|
||||
- layer `active` determines whether the layer participates in publication output;
|
||||
- tile `active` determines whether the tile contributes an active tile record to the published request;
|
||||
- canonical scene persistence must not depend on `TMX`, `TSX`, or any other external file format remaining present on disk.
|
||||
|
||||
@ -1,6 +1,25 @@
|
||||
final_id_hex,final_id_dec,canonical_name,canonical_version,owner,name,arg_slots,ret_slots,arg_layout,ret_layout,deterministic,may_allocate
|
||||
0x1000,4096,vec2.dot,1,vec2,dot,4,1,float|float|float|float,float,true,false
|
||||
0x1001,4097,vec2.length,1,vec2,length,2,1,float|float,float,true,false
|
||||
0x1100,4352,color.from_raw,1,color,from_raw,1,1,int,builtin:color,true,false
|
||||
0x1101,4353,color.rgb,1,color,rgb,3,1,int|int|int,builtin:color,true,false
|
||||
0x1102,4354,color.rgba,1,color,rgba,4,1,int|int|int|int,builtin:color,true,false
|
||||
0x1103,4355,color.html_rgba,1,color,html_rgba,1,1,str,builtin:color,true,false
|
||||
0x1104,4356,color.gray_scale,1,color,gray_scale,1,1,int,builtin:color,true,false
|
||||
0x1105,4357,color.hex,1,color,hex,1,1,builtin:color,int,true,false
|
||||
0x1106,4358,color.alpha,1,color,alpha,1,1,builtin:color,int,true,false
|
||||
0x1120,4384,color.black,1,color,black,0,1,,builtin:color,true,false
|
||||
0x1121,4385,color.white,1,color,white,0,1,,builtin:color,true,false
|
||||
0x1122,4386,color.red,1,color,red,0,1,,builtin:color,true,false
|
||||
0x1123,4387,color.green,1,color,green,0,1,,builtin:color,true,false
|
||||
0x1124,4388,color.blue,1,color,blue,0,1,,builtin:color,true,false
|
||||
0x1125,4389,color.yellow,1,color,yellow,0,1,,builtin:color,true,false
|
||||
0x1126,4390,color.orange,1,color,orange,0,1,,builtin:color,true,false
|
||||
0x1127,4391,color.indigo,1,color,indigo,0,1,,builtin:color,true,false
|
||||
0x1128,4392,color.gray,1,color,gray,0,1,,builtin:color,true,false
|
||||
0x1129,4393,color.cyan,1,color,cyan,0,1,,builtin:color,true,false
|
||||
0x112A,4394,color.magenta,1,color,magenta,0,1,,builtin:color,true,false
|
||||
0x112B,4395,color.transparent,1,color,transparent,0,1,,builtin:color,true,false
|
||||
0x2000,8192,input.pad,1,input,pad,0,1,,builtin:input.pad,true,false
|
||||
0x2001,8193,input.touch,1,input,touch,0,1,,builtin:input.touch,true,false
|
||||
0x2010,8208,input.pad.up,1,input.pad,up,1,1,builtin:input.pad,builtin:input.button,true,false
|
||||
|
||||
|
@ -574,7 +574,30 @@ final class PbsExecutableBodyLowerer {
|
||||
final var constDecl = context.resolveConstDecl(identifierExpr.name());
|
||||
if (constDecl != null && constDecl.initializer() != null) {
|
||||
lowerConstExpression(constDecl.initializer(), context);
|
||||
return;
|
||||
}
|
||||
final var builtinConst = context.builtinConstByNameId().get(context.nameTable().register(identifierExpr.name()));
|
||||
if (builtinConst != null) {
|
||||
emitBuiltinConstFetch(builtinConst, identifierExpr.span(), context);
|
||||
}
|
||||
}
|
||||
|
||||
private void emitBuiltinConstFetch(
|
||||
final p.studio.compiler.models.IRReservedMetadata.BuiltinConstSurface builtinConst,
|
||||
final p.studio.compiler.source.Span span,
|
||||
final PbsExecutableLoweringContext context) {
|
||||
final var canonicalName = builtinConst.canonicalTarget() + "." + builtinConst.canonicalName();
|
||||
context.instructions().add(new IRBackendExecutableFunction.Instruction(
|
||||
IRBackendExecutableFunction.InstructionKind.CALL_INTRINSIC,
|
||||
"",
|
||||
null,
|
||||
new IRBackendExecutableFunction.IntrinsicCallMetadata(
|
||||
canonicalName,
|
||||
builtinConst.canonicalVersion(),
|
||||
context.intrinsicIdTable().register(canonicalName, builtinConst.canonicalVersion())),
|
||||
0,
|
||||
1,
|
||||
span));
|
||||
}
|
||||
|
||||
private String flattenAssetReference(final PbsAst.Expression expression) {
|
||||
|
||||
@ -326,6 +326,10 @@ final class PbsExecutableCallsiteEmitter {
|
||||
if (!localOwner.isBlank()) {
|
||||
return localOwner;
|
||||
}
|
||||
final var builtinOwner = context.builtinCanonicalBySourceType().getOrDefault(identifier, "");
|
||||
if (!builtinOwner.isBlank()) {
|
||||
return builtinOwner;
|
||||
}
|
||||
return context.builtinConstOwnerByNameId().getOrDefault(context.nameTable().register(identifier), "");
|
||||
}
|
||||
|
||||
|
||||
@ -60,6 +60,10 @@ final class PbsExecutableLoweringContext {
|
||||
return metadataIndex.builtinConstOwnerByNameId();
|
||||
}
|
||||
|
||||
Map<NameId, p.studio.compiler.models.IRReservedMetadata.BuiltinConstSurface> builtinConstByNameId() {
|
||||
return metadataIndex.builtinConstByNameId();
|
||||
}
|
||||
|
||||
Map<NameId, Integer> globalSlotByNameId() {
|
||||
return metadataIndex.globalSlotByNameId();
|
||||
}
|
||||
|
||||
@ -45,6 +45,7 @@ public class PbsExecutableLoweringModels {
|
||||
Map<NameId, List<IRReservedMetadata.HostMethodBinding>> hostByMethodName,
|
||||
Map<String, String> builtinCanonicalBySourceType,
|
||||
Map<NameId, String> builtinConstOwnerByNameId,
|
||||
Map<NameId, IRReservedMetadata.BuiltinConstSurface> builtinConstByNameId,
|
||||
Map<NameId, Integer> globalSlotByNameId,
|
||||
Map<NameId, PbsAst.ConstDecl> constDeclByNameId,
|
||||
Map<String, Addressable> addressableByAddress,
|
||||
|
||||
@ -30,6 +30,7 @@ final class PbsExecutableMetadataIndexFactory {
|
||||
final var hostByMethodName = indexHostBindingsByMethodName(reservedMetadata, nameTable);
|
||||
final var builtinCanonicalBySourceType = indexBuiltinCanonicalBySourceType(reservedMetadata);
|
||||
final var builtinConstOwnerByNameId = indexBuiltinConstOwners(reservedMetadata, nameTable);
|
||||
final var builtinConstByNameId = indexBuiltinConstSurfaces(reservedMetadata, nameTable);
|
||||
final var globalSlotByNameId = indexGlobalSlots(ast, nameTable);
|
||||
final var constDeclByNameId = indexConstDecls(ast, nameTable);
|
||||
final var addressableByAddress = indexAddressables(feSurfaceContext);
|
||||
@ -50,6 +51,7 @@ final class PbsExecutableMetadataIndexFactory {
|
||||
hostByMethodName,
|
||||
builtinCanonicalBySourceType,
|
||||
builtinConstOwnerByNameId,
|
||||
builtinConstByNameId,
|
||||
globalSlotByNameId,
|
||||
constDeclByNameId,
|
||||
addressableByAddress,
|
||||
@ -90,6 +92,19 @@ final class PbsExecutableMetadataIndexFactory {
|
||||
return builtinConstOwnerByNameId;
|
||||
}
|
||||
|
||||
private Map<NameId, IRReservedMetadata.BuiltinConstSurface> indexBuiltinConstSurfaces(
|
||||
final IRReservedMetadata reservedMetadata,
|
||||
final NameTable nameTable) {
|
||||
final var builtinConstByNameId = new HashMap<NameId, IRReservedMetadata.BuiltinConstSurface>();
|
||||
for (final var builtinConst : reservedMetadata.builtinConstSurfaces()) {
|
||||
if (builtinConst.sourceConstName().isBlank() || builtinConst.canonicalTarget().isBlank()) {
|
||||
continue;
|
||||
}
|
||||
builtinConstByNameId.put(nameTable.register(builtinConst.sourceConstName()), builtinConst);
|
||||
}
|
||||
return builtinConstByNameId;
|
||||
}
|
||||
|
||||
private Map<NameId, Integer> indexGlobalSlots(
|
||||
final PbsAst.File ast,
|
||||
final NameTable nameTable) {
|
||||
|
||||
@ -86,6 +86,22 @@ final class PbsFlowCallableResolutionAnalyzer {
|
||||
if (enumCases != null && enumCases.contains(memberExpr.memberName())) {
|
||||
return ExprResult.type(TypeView.enumType(receiver.name()));
|
||||
}
|
||||
if (model.knownBuiltinTypeNames.contains(receiver.name())) {
|
||||
final var builtin = model.structs.get(receiver.name());
|
||||
if (builtin != null) {
|
||||
final var methods = builtin.methods().get(memberExpr.memberName());
|
||||
if (methods != null && !methods.isEmpty()) {
|
||||
if (use == ExprUse.VALUE) {
|
||||
Diagnostics.error(diagnostics,
|
||||
PbsSemanticsErrors.E_SEM_BARE_METHOD_EXTRACTION.name(),
|
||||
"Bare method extraction is not allowed in PBS core",
|
||||
memberExpr.span());
|
||||
return ExprResult.type(TypeView.unknown());
|
||||
}
|
||||
return ExprResult.callables(methods, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
Diagnostics.error(diagnostics,
|
||||
PbsSemanticsErrors.E_SEM_INVALID_MEMBER_ACCESS.name(),
|
||||
"Invalid type member access",
|
||||
|
||||
@ -191,6 +191,7 @@ final class PbsFlowSemanticSupport {
|
||||
final Map<String, TypeView> constTypes = new HashMap<>();
|
||||
final Map<String, TypeView> globalTypes = new HashMap<>();
|
||||
final Map<String, TypeView> serviceSingletons = new HashMap<>();
|
||||
final Set<String> knownBuiltinTypeNames = new HashSet<>();
|
||||
final Set<String> knownStructNames = new HashSet<>();
|
||||
final Set<String> knownServiceNames = new HashSet<>();
|
||||
final Set<String> knownContractNames = new HashSet<>();
|
||||
@ -232,6 +233,7 @@ final class PbsFlowSemanticSupport {
|
||||
return;
|
||||
}
|
||||
if (topDecl instanceof PbsAst.BuiltinTypeDecl builtinTypeDecl) {
|
||||
knownBuiltinTypeNames.add(builtinTypeDecl.name());
|
||||
knownStructNames.add(builtinTypeDecl.name());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -108,6 +108,10 @@ final class PbsFlowStructuralExpressionAnalyzer {
|
||||
return ExprResult.type(TypeView.typeRef(identifierExpr.name()));
|
||||
}
|
||||
|
||||
if (model.knownBuiltinTypeNames.contains(identifierExpr.name())) {
|
||||
return ExprResult.type(TypeView.typeRef(identifierExpr.name()));
|
||||
}
|
||||
|
||||
return ExprResult.type(TypeView.unknown());
|
||||
}
|
||||
if (expression instanceof PbsAst.GroupExpr groupExpr) {
|
||||
|
||||
@ -1,5 +1,61 @@
|
||||
[BuiltinType(name = "Color", version = 1)]
|
||||
[BuiltinType(name = "color", version = 1)]
|
||||
declare builtin type Color(
|
||||
pub raw: int
|
||||
) {
|
||||
[IntrinsicCall(name = "from_raw", version = 1)]
|
||||
fn from_raw(raw: int) -> Color;
|
||||
|
||||
[IntrinsicCall(name = "rgb", version = 1)]
|
||||
fn rgb(r: int, g: int, b: int) -> Color;
|
||||
|
||||
[IntrinsicCall(name = "rgba", version = 1)]
|
||||
fn rgba(r: int, g: int, b: int, a: int) -> Color;
|
||||
|
||||
[IntrinsicCall(name = "html_rgba", version = 1)]
|
||||
fn html_rgba(value: str) -> Color;
|
||||
|
||||
[IntrinsicCall(name = "gray_scale", version = 1)]
|
||||
fn gray_scale(value: int) -> Color;
|
||||
|
||||
[IntrinsicCall(name = "hex", version = 1)]
|
||||
fn hex() -> int;
|
||||
|
||||
[IntrinsicCall(name = "alpha", version = 1)]
|
||||
fn alpha() -> int;
|
||||
}
|
||||
|
||||
[BuiltinConst(target = "color", name = "black", version = 1)]
|
||||
declare const BLACK: Color;
|
||||
|
||||
[BuiltinConst(target = "color", name = "white", version = 1)]
|
||||
declare const WHITE: Color;
|
||||
|
||||
[BuiltinConst(target = "color", name = "red", version = 1)]
|
||||
declare const RED: Color;
|
||||
|
||||
[BuiltinConst(target = "color", name = "green", version = 1)]
|
||||
declare const GREEN: Color;
|
||||
|
||||
[BuiltinConst(target = "color", name = "blue", version = 1)]
|
||||
declare const BLUE: Color;
|
||||
|
||||
[BuiltinConst(target = "color", name = "yellow", version = 1)]
|
||||
declare const YELLOW: Color;
|
||||
|
||||
[BuiltinConst(target = "color", name = "orange", version = 1)]
|
||||
declare const ORANGE: Color;
|
||||
|
||||
[BuiltinConst(target = "color", name = "indigo", version = 1)]
|
||||
declare const INDIGO: Color;
|
||||
|
||||
[BuiltinConst(target = "color", name = "gray", version = 1)]
|
||||
declare const GRAY: Color;
|
||||
|
||||
[BuiltinConst(target = "color", name = "cyan", version = 1)]
|
||||
declare const CYAN: Color;
|
||||
|
||||
[BuiltinConst(target = "color", name = "magenta", version = 1)]
|
||||
declare const MAGENTA: Color;
|
||||
|
||||
[BuiltinConst(target = "color", name = "transparent", version = 1)]
|
||||
declare const TRANSPARENT: Color;
|
||||
|
||||
@ -1 +1,13 @@
|
||||
pub struct Color;
|
||||
pub const BLACK;
|
||||
pub const WHITE;
|
||||
pub const RED;
|
||||
pub const GREEN;
|
||||
pub const BLUE;
|
||||
pub const YELLOW;
|
||||
pub const ORANGE;
|
||||
pub const INDIGO;
|
||||
pub const GRAY;
|
||||
pub const CYAN;
|
||||
pub const MAGENTA;
|
||||
pub const TRANSPARENT;
|
||||
|
||||
@ -28,10 +28,6 @@ declare host LowGfx {
|
||||
[Host(module = "gfx", name = "draw_text", version = 1)]
|
||||
[Capability(name = "gfx")]
|
||||
fn draw_text(x: int, y: int, message: str, color: Color) -> void;
|
||||
|
||||
[Host(module = "gfx", name = "clear_565", version = 1)]
|
||||
[Capability(name = "gfx")]
|
||||
fn clear_565(color_565: int) -> void;
|
||||
}
|
||||
|
||||
declare service Gfx
|
||||
@ -70,9 +66,4 @@ declare service Gfx
|
||||
{
|
||||
LowGfx.draw_text(x, y, message, color);
|
||||
}
|
||||
|
||||
fn clear_565(color_565: int) -> void
|
||||
{
|
||||
LowGfx.clear_565(color_565);
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,18 +73,15 @@ declare builtin type InputButton() {
|
||||
fn hold() -> int;
|
||||
}
|
||||
|
||||
[BuiltinConst(target = "input", name = "global", version = 1)]
|
||||
declare const LowInput: InputType;
|
||||
|
||||
declare service Input
|
||||
{
|
||||
fn pad() -> InputPad
|
||||
{
|
||||
return LowInput.pad();
|
||||
return InputType.pad();
|
||||
}
|
||||
|
||||
fn touch() -> InputTouch
|
||||
{
|
||||
return LowInput.touch();
|
||||
return InputType.touch();
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,8 +107,8 @@ class PbsGateUSdkInterfaceConformanceTest {
|
||||
.anyMatch(t -> t.sourceTypeName().equals("Color")));
|
||||
assertTrue(positive.irBackend().getReservedMetadata().builtinTypeSurfaces().stream()
|
||||
.anyMatch(t -> t.sourceTypeName().equals("InputType") && t.canonicalTypeName().equals("input")));
|
||||
assertTrue(positive.irBackend().getReservedMetadata().builtinConstSurfaces().stream()
|
||||
.anyMatch(c -> c.sourceConstName().equals("LowInput") && c.canonicalTarget().equals("input")));
|
||||
assertFalse(positive.irBackend().getReservedMetadata().builtinConstSurfaces().stream()
|
||||
.anyMatch(c -> c.canonicalTarget().equals("input")));
|
||||
assertTrue(positive.irBackend().getReservedMetadata().builtinTypeSurfaces().stream()
|
||||
.anyMatch(t -> t.sourceTypeName().equals("InputButton")
|
||||
&& t.intrinsics().stream().anyMatch(i -> i.canonicalName().equals("input.button.hold"))));
|
||||
@ -194,15 +194,12 @@ class PbsGateUSdkInterfaceConformanceTest {
|
||||
fn x() -> int;
|
||||
}
|
||||
|
||||
[BuiltinConst(target = "input", name = "global", version = 1)]
|
||||
declare const LowInput: InputType;
|
||||
|
||||
declare service Input {
|
||||
fn touch() -> InputTouch {
|
||||
if (true) {
|
||||
return LowInput.touch();
|
||||
return InputType.touch();
|
||||
}
|
||||
return LowInput.touch();
|
||||
return InputType.touch();
|
||||
}
|
||||
}
|
||||
""",
|
||||
|
||||
@ -977,12 +977,13 @@ class PBSFrontendPhaseServiceTest {
|
||||
final var sourceFile = modulePath.resolve("source.pbs");
|
||||
final var modBarrel = modulePath.resolve("mod.barrel");
|
||||
Files.writeString(sourceFile, """
|
||||
import { Color } from @core:color;
|
||||
import { Gfx } from @sdk:gfx;
|
||||
|
||||
[Frame]
|
||||
fn frame() -> void
|
||||
{
|
||||
Gfx.clear_565(6577);
|
||||
Gfx.clear(new Color(6577));
|
||||
}
|
||||
""");
|
||||
Files.writeString(modBarrel, "pub fn frame() -> void;");
|
||||
@ -1018,6 +1019,74 @@ class PBSFrontendPhaseServiceTest {
|
||||
assertTrue(irBackend.getExecutableFunctions().stream().anyMatch(function -> "frame".equals(function.callableName())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldLowerCoreColorConstructorsAndConstantsFromBootstrapStdlib() throws IOException {
|
||||
final var projectRoot = tempDir.resolve("project-bootstrap-core-color-rgba");
|
||||
final var sourceRoot = projectRoot.resolve("src");
|
||||
final var modulePath = sourceRoot.resolve("app");
|
||||
Files.createDirectories(modulePath);
|
||||
|
||||
final var sourceFile = modulePath.resolve("source.pbs");
|
||||
final var modBarrel = modulePath.resolve("mod.barrel");
|
||||
Files.writeString(sourceFile, """
|
||||
import { Color, RED, TRANSPARENT } from @core:color;
|
||||
import { Gfx } from @sdk:gfx;
|
||||
|
||||
[Frame]
|
||||
fn frame() -> void
|
||||
{
|
||||
let tint: Color = Color.rgba(1, 2, 3, 128);
|
||||
let web: Color = Color.html_rgba("#11223344");
|
||||
Gfx.clear(RED);
|
||||
Gfx.fill_rect(0, 0, 1, 1, tint);
|
||||
Gfx.fill_rect(1, 0, 1, 1, web);
|
||||
Gfx.fill_rect(2, 0, 1, 1, TRANSPARENT);
|
||||
}
|
||||
""");
|
||||
Files.writeString(modBarrel, "pub fn frame() -> void;");
|
||||
|
||||
final var projectTable = new ProjectTable();
|
||||
final var fileTable = new FileTable(1);
|
||||
final var projectId = projectTable.register(ProjectDescriptor.builder()
|
||||
.rootPath(projectRoot)
|
||||
.name("app")
|
||||
.version("1.0.0")
|
||||
.sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
|
||||
.build());
|
||||
|
||||
registerFile(projectId, projectRoot, sourceFile, fileTable);
|
||||
registerFile(projectId, projectRoot, modBarrel, fileTable);
|
||||
|
||||
final var ctx = new FrontendPhaseContext(
|
||||
projectTable,
|
||||
fileTable,
|
||||
new BuildStack(ReadOnlyList.wrap(List.of(projectId))),
|
||||
1,
|
||||
AppMode.Game);
|
||||
final var diagnostics = DiagnosticSink.empty();
|
||||
|
||||
final var irBackend = new PBSFrontendPhaseService().compile(
|
||||
ctx,
|
||||
diagnostics,
|
||||
LogAggregator.empty(),
|
||||
BuildingIssueSink.empty());
|
||||
|
||||
assertFalse(diagnostics.hasErrors(),
|
||||
"diagnostics=" + diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList());
|
||||
assertTrue(diagnostics.stream().noneMatch(d ->
|
||||
d.getCode().equals(PbsSemanticsErrors.E_SEM_EXEC_LOWERING_UNRESOLVED_CALLEE.name())),
|
||||
"diagnostics=" + diagnostics.stream().map(d -> d.getCode() + ":" + d.getMessage()).toList());
|
||||
final var intrinsicNames = irBackend.getExecutableFunctions().stream()
|
||||
.flatMap(function -> function.instructions().stream())
|
||||
.filter(instruction -> instruction.kind() == IRBackendExecutableFunction.InstructionKind.CALL_INTRINSIC)
|
||||
.map(instruction -> instruction.intrinsicCall().canonicalName())
|
||||
.toList();
|
||||
assertTrue(intrinsicNames.contains("color.rgba"), "intrinsics=" + intrinsicNames);
|
||||
assertTrue(intrinsicNames.contains("color.html_rgba"), "intrinsics=" + intrinsicNames);
|
||||
assertTrue(intrinsicNames.contains("color.red"), "intrinsics=" + intrinsicNames);
|
||||
assertTrue(intrinsicNames.contains("color.transparent"), "intrinsics=" + intrinsicNames);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldLowerSdkComposerEmitSpriteFacadeUsingCanonicalComposerContract() throws IOException {
|
||||
final var projectRoot = tempDir.resolve("project-bootstrap-sdk-composer-emit-sprite");
|
||||
|
||||
@ -5,6 +5,25 @@ import java.util.Objects;
|
||||
enum IRVMIntrinsicDefinition {
|
||||
VEC2_DOT("vec2.dot", 1, 0x1000, "vec2", "dot", 4, 1, "float|float|float|float", "float", true, false),
|
||||
VEC2_LENGTH("vec2.length", 1, 0x1001, "vec2", "length", 2, 1, "float|float", "float", true, false),
|
||||
COLOR_FROM_RAW("color.from_raw", 1, 0x1100, "color", "from_raw", 1, 1, "int", "builtin:color", true, false),
|
||||
COLOR_RGB("color.rgb", 1, 0x1101, "color", "rgb", 3, 1, "int|int|int", "builtin:color", true, false),
|
||||
COLOR_RGBA("color.rgba", 1, 0x1102, "color", "rgba", 4, 1, "int|int|int|int", "builtin:color", true, false),
|
||||
COLOR_HTML_RGBA("color.html_rgba", 1, 0x1103, "color", "html_rgba", 1, 1, "str", "builtin:color", true, false),
|
||||
COLOR_GRAY_SCALE("color.gray_scale", 1, 0x1104, "color", "gray_scale", 1, 1, "int", "builtin:color", true, false),
|
||||
COLOR_HEX("color.hex", 1, 0x1105, "color", "hex", 1, 1, "builtin:color", "int", true, false),
|
||||
COLOR_ALPHA("color.alpha", 1, 0x1106, "color", "alpha", 1, 1, "builtin:color", "int", true, false),
|
||||
COLOR_BLACK("color.black", 1, 0x1120, "color", "black", 0, 1, "", "builtin:color", true, false),
|
||||
COLOR_WHITE("color.white", 1, 0x1121, "color", "white", 0, 1, "", "builtin:color", true, false),
|
||||
COLOR_RED("color.red", 1, 0x1122, "color", "red", 0, 1, "", "builtin:color", true, false),
|
||||
COLOR_GREEN("color.green", 1, 0x1123, "color", "green", 0, 1, "", "builtin:color", true, false),
|
||||
COLOR_BLUE("color.blue", 1, 0x1124, "color", "blue", 0, 1, "", "builtin:color", true, false),
|
||||
COLOR_YELLOW("color.yellow", 1, 0x1125, "color", "yellow", 0, 1, "", "builtin:color", true, false),
|
||||
COLOR_ORANGE("color.orange", 1, 0x1126, "color", "orange", 0, 1, "", "builtin:color", true, false),
|
||||
COLOR_INDIGO("color.indigo", 1, 0x1127, "color", "indigo", 0, 1, "", "builtin:color", true, false),
|
||||
COLOR_GRAY("color.gray", 1, 0x1128, "color", "gray", 0, 1, "", "builtin:color", true, false),
|
||||
COLOR_CYAN("color.cyan", 1, 0x1129, "color", "cyan", 0, 1, "", "builtin:color", true, false),
|
||||
COLOR_MAGENTA("color.magenta", 1, 0x112A, "color", "magenta", 0, 1, "", "builtin:color", true, false),
|
||||
COLOR_TRANSPARENT("color.transparent", 1, 0x112B, "color", "transparent", 0, 1, "", "builtin:color", true, false),
|
||||
INPUT_PAD("input.pad", 1, 0x2000, "input", "pad", 0, 1, "", "builtin:input.pad", true, false),
|
||||
INPUT_TOUCH("input.touch", 1, 0x2001, "input", "touch", 0, 1, "", "builtin:input.touch", true, false),
|
||||
INPUT_PAD_UP("input.pad.up", 1, 0x2010, "input.pad", "up", 1, 1, "builtin:input.pad", "builtin:input.button", true, false),
|
||||
|
||||
@ -12,7 +12,7 @@ import java.util.regex.Pattern;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@Disabled
|
||||
@Disabled("parity test for intrinsic registry, should be enabled when modifying intrinsics definitions or spec")
|
||||
class IRVMIntrinsicRegistryParityTest {
|
||||
private static final String VM_ARCH_INTRINSICS_PATH = "docs/vm-arch/INTRINSICS.csv";
|
||||
private static final String RUNTIME_INTRINSICS_PATH = "crates/console/prometeu-vm/src/builtins.rs";
|
||||
|
||||
@ -28,7 +28,7 @@ import java.util.Set;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@Disabled
|
||||
@Disabled("tests for gate I validation of emitted bytecode, requires direct construction of IRBackend and BytecodeModule instances")
|
||||
class BackendGateIIntegrationTest {
|
||||
private final RuntimeCompatibilityAdapter compatibilityAdapter = RuntimeCompatibilityAdapters.createDefault();
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package p.studio.compiler.integration;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import p.studio.compiler.messages.BuilderPipelineConfig;
|
||||
import p.studio.compiler.models.BuilderPipelineContext;
|
||||
@ -15,6 +16,7 @@ import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@Disabled("integration test for main project pipeline, requires test-projects/main to be present in the repository")
|
||||
class MainProjectPipelineIntegrationTest {
|
||||
|
||||
@Test
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package p.studio.lsp.services.compiler;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import p.studio.lsp.messages.BaselineIssueSeverity;
|
||||
import p.studio.lsp.messages.LspProjectContext;
|
||||
@ -165,6 +166,7 @@ class CompilerLanguageServiceBridgeTest {
|
||||
assertEquals("Vec()", constructorSignatureHelp.signatures().getFirst().label());
|
||||
}
|
||||
|
||||
@Disabled
|
||||
@Test
|
||||
void completionHoverAndSignatureHelpUseStdlibSemanticParameterNames() {
|
||||
final Path projectRoot = findRepoRoot(Path.of("").toAbsolutePath().normalize())
|
||||
@ -177,13 +179,13 @@ class CompilerLanguageServiceBridgeTest {
|
||||
import { Gfx } from @sdk:gfx;
|
||||
|
||||
fn frame() -> void {
|
||||
Gfx.clear_565(5);
|
||||
Gfx.clear(5);
|
||||
}
|
||||
""";
|
||||
final CompilerLanguageServiceBridge bridge = new CompilerLanguageServiceBridge();
|
||||
final LspProjectContext context = new LspProjectContext("main", "pbs", projectRoot);
|
||||
final DocumentPositionMapper mapper = new DocumentPositionMapper(overlay);
|
||||
final var memberPosition = mapper.positionOf(overlay.indexOf("clear_565"));
|
||||
final var memberPosition = mapper.positionOf(overlay.indexOf("clear"));
|
||||
final var signaturePosition = mapper.positionOf(overlay.indexOf("5);"));
|
||||
|
||||
final var completion = bridge.completion(
|
||||
@ -193,10 +195,10 @@ class CompilerLanguageServiceBridgeTest {
|
||||
memberPosition.line(),
|
||||
memberPosition.character());
|
||||
final var clear565Completion = completion.items().stream()
|
||||
.filter(item -> item.label().equals("clear_565"))
|
||||
.filter(item -> item.label().equals("clear"))
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
assertEquals("clear_565(color_565: int) -> void", clear565Completion.detail());
|
||||
assertEquals("clear(color: int) -> void", clear565Completion.detail());
|
||||
assertFalse(clear565Completion.detail().contains("arg0"));
|
||||
|
||||
final var hover = bridge.hover(
|
||||
@ -205,7 +207,7 @@ class CompilerLanguageServiceBridgeTest {
|
||||
overlay,
|
||||
memberPosition.line(),
|
||||
memberPosition.character());
|
||||
assertTrue(hover.markdown().contains("clear_565(color_565: int) -> void"));
|
||||
assertTrue(hover.markdown().contains("clear(color: int) -> void"));
|
||||
assertFalse(hover.markdown().contains("arg0"));
|
||||
|
||||
final var signatureHelp = bridge.signatureHelp(
|
||||
@ -214,8 +216,8 @@ class CompilerLanguageServiceBridgeTest {
|
||||
overlay,
|
||||
signaturePosition.line(),
|
||||
signaturePosition.character());
|
||||
assertEquals("clear_565(color_565: int) -> void", signatureHelp.signatures().getFirst().label());
|
||||
assertEquals(List.of("color_565: int"), signatureHelp.signatures().getFirst().parameters());
|
||||
assertEquals("clear(color: int) -> void", signatureHelp.signatures().getFirst().label());
|
||||
assertEquals(List.of("color: int"), signatureHelp.signatures().getFirst().parameters());
|
||||
assertFalse(signatureHelp.signatures().getFirst().label().contains("arg0"));
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package p.packer.models;
|
||||
|
||||
public record PackerPaletteV1(
|
||||
int[] originalArgb8888,
|
||||
short[] convertedRgb565) {
|
||||
int[] rgba8888) {
|
||||
}
|
||||
@ -349,10 +349,7 @@ public class PackerAssetWalker {
|
||||
for (byte value : tile.paletteIndices()) {
|
||||
maxUsedIndex = Math.max(maxUsedIndex, Byte.toUnsignedInt(value));
|
||||
}
|
||||
if (maxUsedIndex == 0) {
|
||||
continue;
|
||||
}
|
||||
final int requiredColorCount = maxUsedIndex;
|
||||
final int requiredColorCount = maxUsedIndex + 1;
|
||||
final Optional<DeclaredPalette> fragilePalette = declaredPalettes.stream()
|
||||
.filter(palette -> palette.colorCount() < requiredColorCount)
|
||||
.findFirst();
|
||||
@ -383,13 +380,10 @@ public class PackerAssetWalker {
|
||||
if (!indexNode.isInt() || !paletteNode.isObject()) {
|
||||
continue;
|
||||
}
|
||||
final int originalCount = paletteNode.path("originalArgb8888").isArray()
|
||||
? paletteNode.path("originalArgb8888").size()
|
||||
final int colorCount = paletteNode.path("rgba8888").isArray()
|
||||
? paletteNode.path("rgba8888").size()
|
||||
: 0;
|
||||
final int convertedCount = paletteNode.path("convertedRgb565").isArray()
|
||||
? paletteNode.path("convertedRgb565").size()
|
||||
: 0;
|
||||
palettes.add(new DeclaredPalette(indexNode.intValue(), Math.min(originalCount, convertedCount)));
|
||||
palettes.add(new DeclaredPalette(indexNode.intValue(), colorCount));
|
||||
}
|
||||
palettes.sort(java.util.Comparator.comparingInt(DeclaredPalette::index));
|
||||
return List.copyOf(palettes);
|
||||
|
||||
@ -11,8 +11,7 @@ import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
public class PackerGlyphBankWalker extends PackerAbstractBankWalker<PackerGlyphBankRequirements> {
|
||||
private static final int MAX_COLORS_PER_PALETTE = 15;
|
||||
private static final int COLOR_KEY_RGB = 0x00FF00FF;
|
||||
private static final int MAX_COLORS_PER_PALETTE = 16;
|
||||
private static final Set<String> SUPPORTED_MIME_TYPES = Set.of(
|
||||
"image/png"
|
||||
);
|
||||
@ -69,58 +68,37 @@ public class PackerGlyphBankWalker extends PackerAbstractBankWalker<PackerGlyphB
|
||||
));
|
||||
}
|
||||
|
||||
final Map<Integer, Integer> paletteIndexByRgb = new LinkedHashMap<>();
|
||||
final Map<Integer, Integer> paletteIndexByRgba = new LinkedHashMap<>();
|
||||
final byte[] paletteIndices = new byte[image.getWidth() * image.getHeight()];
|
||||
boolean partialAlphaFound = false;
|
||||
boolean exceededPaletteLimit = false;
|
||||
|
||||
int offset = 0;
|
||||
for (var y = 0; y < image.getHeight(); y++) {
|
||||
for (var x = 0; x < image.getWidth(); x++) {
|
||||
final int argb = image.getRGB(x, y);
|
||||
final int alpha = (argb >>> 24) & 0xFF;
|
||||
final int rgb = argb & 0x00FFFFFF;
|
||||
final int rgba = argbToRgba(argb);
|
||||
|
||||
if (alpha == 0 || rgb == COLOR_KEY_RGB) {
|
||||
paletteIndices[offset++] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (alpha < 0xFF) {
|
||||
partialAlphaFound = true;
|
||||
}
|
||||
|
||||
Integer paletteIndex = paletteIndexByRgb.get(rgb);
|
||||
Integer paletteIndex = paletteIndexByRgba.get(rgba);
|
||||
if (paletteIndex == null) {
|
||||
if (paletteIndexByRgb.size() >= MAX_COLORS_PER_PALETTE) {
|
||||
if (paletteIndexByRgba.size() >= MAX_COLORS_PER_PALETTE) {
|
||||
exceededPaletteLimit = true;
|
||||
paletteIndices[offset++] = 0;
|
||||
continue;
|
||||
}
|
||||
paletteIndex = paletteIndexByRgb.size() + 1;
|
||||
paletteIndexByRgb.put(rgb, paletteIndex);
|
||||
paletteIndex = paletteIndexByRgba.size();
|
||||
paletteIndexByRgba.put(rgba, paletteIndex);
|
||||
}
|
||||
|
||||
paletteIndices[offset++] = paletteIndex.byteValue();
|
||||
}
|
||||
}
|
||||
|
||||
if (partialAlphaFound) {
|
||||
diagnostics.add(new PackerDiagnostic(
|
||||
PackerDiagnosticSeverity.WARNING,
|
||||
PackerDiagnosticCategory.HYGIENE,
|
||||
"Tile contains partial alpha; flattening to RGB and ignoring alpha channel",
|
||||
fileProbe.path(),
|
||||
false
|
||||
));
|
||||
}
|
||||
|
||||
if (exceededPaletteLimit) {
|
||||
diagnostics.add(new PackerDiagnostic(
|
||||
PackerDiagnosticSeverity.ERROR,
|
||||
PackerDiagnosticCategory.STRUCTURAL,
|
||||
"Tile image exceeds color limit for " + fileProbe.path().getFileName()
|
||||
+ ": expected at most " + MAX_COLORS_PER_PALETTE + " colors for indices 1..15",
|
||||
+ ": expected at most " + MAX_COLORS_PER_PALETTE + " RGBA8888 colors",
|
||||
fileProbe.path(),
|
||||
true
|
||||
));
|
||||
@ -131,7 +109,7 @@ public class PackerGlyphBankWalker extends PackerAbstractBankWalker<PackerGlyphB
|
||||
}
|
||||
|
||||
final var tile = new PackerTileIndexedV1(image.getWidth(), image.getHeight(), paletteIndices);
|
||||
final var palette = buildPalette(paletteIndexByRgb);
|
||||
final var palette = buildPalette(paletteIndexByRgba);
|
||||
// Disabled for now: derived tile/palette JSON artifacts are not emitted during normal walks.
|
||||
// If we want filesystem-side debug artifacts again, uncomment the call below.
|
||||
// serializeProbeArtifacts(fileProbe, assetRoot, tile, palette, diagnostics);
|
||||
@ -150,7 +128,7 @@ public class PackerGlyphBankWalker extends PackerAbstractBankWalker<PackerGlyphB
|
||||
final Object width = tile.get("width");
|
||||
final Object height = tile.get("height");
|
||||
final Object indices = tile.get("paletteIndices");
|
||||
final Object colors = palette.get("originalArgb8888");
|
||||
final Object colors = palette.get("rgba8888");
|
||||
return width instanceof Number
|
||||
&& height instanceof Number
|
||||
&& indices instanceof List<?>
|
||||
@ -224,39 +202,27 @@ public class PackerGlyphBankWalker extends PackerAbstractBankWalker<PackerGlyphB
|
||||
}
|
||||
|
||||
private Map<String, Object> toPalettePayload(final PackerPaletteV1 palette) {
|
||||
final List<Integer> convertedRgb565 = new ArrayList<>(palette.convertedRgb565().length);
|
||||
for (final short value : palette.convertedRgb565()) {
|
||||
convertedRgb565.add(Short.toUnsignedInt(value));
|
||||
}
|
||||
|
||||
return Map.of(
|
||||
"originalArgb8888", palette.originalArgb8888(),
|
||||
"convertedRgb565", convertedRgb565
|
||||
"rgba8888", palette.rgba8888()
|
||||
);
|
||||
}
|
||||
|
||||
private PackerPaletteV1 buildPalette(final Map<Integer, Integer> paletteIndexByRgb) {
|
||||
final int[] originalArgb8888 = new int[paletteIndexByRgb.size()];
|
||||
final short[] convertedRgb565 = new short[paletteIndexByRgb.size()];
|
||||
for (final var entry : paletteIndexByRgb.entrySet()) {
|
||||
final int rgb = entry.getKey();
|
||||
final int index = entry.getValue() - 1;
|
||||
final int argb = 0xFF000000 | rgb;
|
||||
originalArgb8888[index] = argb;
|
||||
convertedRgb565[index] = convertToRgb565(argb);
|
||||
private PackerPaletteV1 buildPalette(final Map<Integer, Integer> paletteIndexByRgba) {
|
||||
final int[] rgba8888 = new int[paletteIndexByRgba.size()];
|
||||
for (final var entry : paletteIndexByRgba.entrySet()) {
|
||||
final int rgba = entry.getKey();
|
||||
final int index = entry.getValue();
|
||||
rgba8888[index] = rgba;
|
||||
}
|
||||
return new PackerPaletteV1(originalArgb8888, convertedRgb565);
|
||||
return new PackerPaletteV1(rgba8888);
|
||||
}
|
||||
|
||||
private short convertToRgb565(final int argb) {
|
||||
final int red = (argb >> 16) & 0xFF;
|
||||
final int green = (argb >> 8) & 0xFF;
|
||||
private int argbToRgba(final int argb) {
|
||||
final int alpha = (argb >>> 24) & 0xFF;
|
||||
final int red = (argb >>> 16) & 0xFF;
|
||||
final int green = (argb >>> 8) & 0xFF;
|
||||
final int blue = argb & 0xFF;
|
||||
|
||||
final int r5 = (red >> 3) & 0x1F;
|
||||
final int g6 = (green >> 2) & 0x3F;
|
||||
final int b5 = (blue >> 3) & 0x1F;
|
||||
return (short) ((r5 << 11) | (g6 << 5) | b5);
|
||||
return (red << 24) | (green << 16) | (blue << 8) | alpha;
|
||||
}
|
||||
|
||||
private java.awt.image.BufferedImage readImage(
|
||||
|
||||
@ -38,7 +38,11 @@ import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public final class FileSystemPackerWorkspaceService implements PackerWorkspaceService {
|
||||
private static final int GLYPH_BANK_COLOR_KEY_RGB565 = 0xF81F;
|
||||
private static final int GLYPH_BANK_PALETTE_COUNT = 64;
|
||||
private static final int GLYPH_BANK_COLORS_PER_PALETTE = 16;
|
||||
private static final int GLYPH_BANK_RGBA8888_BYTES_PER_COLOR = 4;
|
||||
private static final int GLYPH_BANK_PALETTE_BYTES =
|
||||
GLYPH_BANK_PALETTE_COUNT * GLYPH_BANK_COLORS_PER_PALETTE * GLYPH_BANK_RGBA8888_BYTES_PER_COLOR;
|
||||
private static final String SCENE_BANK_SUPPORT_FILE = "scene-bank.studio.json";
|
||||
private static final String SCENE_PACK_REQUEST_FILE = "scene-bank.pack-request.json";
|
||||
|
||||
@ -1091,7 +1095,7 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
|
||||
final byte[] sheetPixels = new byte[width * height];
|
||||
final Map<String, PackerRuntimeWalkFile> selectedByPath = new LinkedHashMap<>();
|
||||
runtimeAsset.walkProjection().buildCandidateFiles().forEach(file -> selectedByPath.put(file.relativePath(), file));
|
||||
final Map<Integer, Integer> declaredPaletteIndexByRgb = declaredPaletteIndexByRgb(declaration);
|
||||
final Map<Integer, Integer> declaredPaletteIndexByRgba = declaredPaletteIndexByRgba(declaration);
|
||||
|
||||
declaration.artifacts().stream()
|
||||
.sorted(Comparator.comparingInt(PackerAssetArtifactSelection::index))
|
||||
@ -1108,7 +1112,7 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
|
||||
tile,
|
||||
walkFile.metadata().get("palette"),
|
||||
artifact.file(),
|
||||
declaredPaletteIndexByRgb);
|
||||
declaredPaletteIndexByRgba);
|
||||
final int tileX = (artifact.index() % tilesPerRow) * tileSize;
|
||||
final int tileY = (artifact.index() / tilesPerRow) * tileSize;
|
||||
for (int localY = 0; localY < tile.height(); localY += 1) {
|
||||
@ -1130,7 +1134,7 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
|
||||
runtimeMetadata.put("tile_size", tileSize);
|
||||
runtimeMetadata.put("width", width);
|
||||
runtimeMetadata.put("height", height);
|
||||
runtimeMetadata.put("palette_count", 64);
|
||||
runtimeMetadata.put("palette_count", GLYPH_BANK_PALETTE_COUNT);
|
||||
runtimeMetadata.put("palette_authored", countAuthoredGlyphBankPalettes(declaration));
|
||||
runtimeMetadata.put("codec", Map.of());
|
||||
declaration.outputMetadata().forEach((key, value) -> {
|
||||
@ -1150,7 +1154,7 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
|
||||
"GLYPH",
|
||||
"NONE",
|
||||
payload,
|
||||
width * height + 2048,
|
||||
width * height + GLYPH_BANK_PALETTE_BYTES,
|
||||
runtimeMetadata,
|
||||
toolingMetadata,
|
||||
declaration.preloadEnabled());
|
||||
@ -1160,23 +1164,20 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
|
||||
PackerTileIndexedV1 tile,
|
||||
Object paletteMetadata,
|
||||
String artifactFile,
|
||||
Map<Integer, Integer> declaredPaletteIndexByRgb) {
|
||||
final int[] originalArgb8888 = originalPaletteColors(paletteMetadata);
|
||||
if (originalArgb8888.length == 0) {
|
||||
Map<Integer, Integer> declaredPaletteIndexByRgba) {
|
||||
final int[] rgba8888 = paletteColors(paletteMetadata);
|
||||
if (rgba8888.length == 0) {
|
||||
throw new IllegalStateException("Selected tile artifact is missing palette metadata required for palette remapping: " + artifactFile);
|
||||
}
|
||||
final byte[] remapped = Arrays.copyOf(tile.paletteIndices(), tile.paletteIndices().length);
|
||||
for (int index = 0; index < remapped.length; index += 1) {
|
||||
final int paletteIndex = Byte.toUnsignedInt(remapped[index]);
|
||||
if (paletteIndex == 0) {
|
||||
continue;
|
||||
}
|
||||
if (paletteIndex > originalArgb8888.length) {
|
||||
if (paletteIndex >= rgba8888.length) {
|
||||
throw new IllegalStateException("Tile palette index exceeds discovered palette size for artifact: " + artifactFile);
|
||||
}
|
||||
final int originalArgb = originalArgb8888[paletteIndex - 1];
|
||||
final Integer materialIndex = declaredPaletteIndexByRgb.get(rgb24(originalArgb));
|
||||
if (materialIndex == null || materialIndex < 1 || materialIndex > 15) {
|
||||
final int rgba = rgba8888[paletteIndex];
|
||||
final Integer materialIndex = declaredPaletteIndexByRgba.get(rgba);
|
||||
if (materialIndex == null || materialIndex < 0 || materialIndex > 15) {
|
||||
throw new IllegalStateException("Unable to resolve canonical palette index for artifact color in " + artifactFile);
|
||||
}
|
||||
remapped[index] = (byte) (materialIndex & 0x0F);
|
||||
@ -1184,34 +1185,34 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
|
||||
return remapped;
|
||||
}
|
||||
|
||||
private Map<Integer, Integer> declaredPaletteIndexByRgb(PackerAssetDeclaration declaration) {
|
||||
private Map<Integer, Integer> declaredPaletteIndexByRgba(PackerAssetDeclaration declaration) {
|
||||
final JsonNode palettesNode = declaration.outputPipelineMetadata().get("palettes");
|
||||
if (!(palettesNode instanceof ArrayNode palettesArray) || palettesArray.isEmpty()) {
|
||||
throw new IllegalStateException("Glyph packing requires at least one declared pipeline palette.");
|
||||
}
|
||||
final JsonNode paletteNode = palettesArray.get(0).path("palette").path("originalArgb8888");
|
||||
final JsonNode paletteNode = palettesArray.get(0).path("palette").path("rgba8888");
|
||||
if (!paletteNode.isArray() || paletteNode.isEmpty()) {
|
||||
throw new IllegalStateException("Glyph packing requires originalArgb8888 entries in the declared pipeline palette.");
|
||||
throw new IllegalStateException("Glyph packing requires rgba8888 entries in the declared pipeline palette.");
|
||||
}
|
||||
final LinkedHashMap<Integer, Integer> mapping = new LinkedHashMap<>();
|
||||
for (int index = 0; index < paletteNode.size(); index += 1) {
|
||||
final JsonNode colorNode = paletteNode.get(index);
|
||||
if (!colorNode.canConvertToInt()) {
|
||||
throw new IllegalStateException("Declared pipeline palette contains a non-integer originalArgb8888 entry.");
|
||||
throw new IllegalStateException("Declared pipeline palette contains a non-integer rgba8888 entry.");
|
||||
}
|
||||
mapping.put(rgb24(colorNode.intValue()), index + 1);
|
||||
mapping.put(colorNode.intValue(), index);
|
||||
}
|
||||
return Map.copyOf(mapping);
|
||||
}
|
||||
|
||||
private int[] originalPaletteColors(Object paletteMetadata) {
|
||||
private int[] paletteColors(Object paletteMetadata) {
|
||||
if (paletteMetadata instanceof PackerPaletteV1 palette) {
|
||||
return palette.originalArgb8888();
|
||||
return palette.rgba8888();
|
||||
}
|
||||
if (!(paletteMetadata instanceof Map<?, ?> map)) {
|
||||
return new int[0];
|
||||
}
|
||||
final Object colors = map.get("originalArgb8888");
|
||||
final Object colors = map.get("rgba8888");
|
||||
if (!(colors instanceof List<?> list)) {
|
||||
return new int[0];
|
||||
}
|
||||
@ -1226,10 +1227,6 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
|
||||
return values;
|
||||
}
|
||||
|
||||
private int rgb24(int argb) {
|
||||
return argb & 0x00FFFFFF;
|
||||
}
|
||||
|
||||
private PackedAsset packSceneBank(PackerRuntimeAsset runtimeAsset, PackerAssetDeclaration declaration) {
|
||||
final ScenePackRequest request = runtimeAsset.walkProjection().buildCandidateFiles().stream()
|
||||
.map(file -> file.metadata().get("scenePackRequest"))
|
||||
@ -1279,7 +1276,7 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
|
||||
}
|
||||
|
||||
private byte[] emitGlyphBankPalettes(PackerAssetDeclaration declaration) {
|
||||
final byte[] bytes = new byte[64 * 16 * 2];
|
||||
final byte[] bytes = new byte[GLYPH_BANK_PALETTE_BYTES];
|
||||
final JsonNode palettesNode = declaration.outputPipelineMetadata().get("palettes");
|
||||
if (!(palettesNode instanceof com.fasterxml.jackson.databind.node.ArrayNode palettesArray)) {
|
||||
return bytes;
|
||||
@ -1291,21 +1288,21 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
|
||||
continue;
|
||||
}
|
||||
final int paletteIndex = indexNode.intValue();
|
||||
if (paletteIndex < 0 || paletteIndex >= 64) {
|
||||
if (paletteIndex < 0 || paletteIndex >= GLYPH_BANK_PALETTE_COUNT) {
|
||||
continue;
|
||||
}
|
||||
final int paletteBaseOffset = (paletteIndex * 16) * 2;
|
||||
bytes[paletteBaseOffset] = (byte) (GLYPH_BANK_COLOR_KEY_RGB565 & 0xFF);
|
||||
bytes[paletteBaseOffset + 1] = (byte) ((GLYPH_BANK_COLOR_KEY_RGB565 >>> 8) & 0xFF);
|
||||
final JsonNode convertedNode = paletteNode.path("convertedRgb565");
|
||||
if (!convertedNode.isArray()) {
|
||||
final JsonNode rgbaNode = paletteNode.path("rgba8888");
|
||||
if (!rgbaNode.isArray()) {
|
||||
continue;
|
||||
}
|
||||
for (int colorIndex = 0; colorIndex < Math.min(15, convertedNode.size()); colorIndex += 1) {
|
||||
final int rgb565 = convertedNode.get(colorIndex).asInt(0);
|
||||
final int baseOffset = ((paletteIndex * 16) + colorIndex + 1) * 2;
|
||||
bytes[baseOffset] = (byte) (rgb565 & 0xFF);
|
||||
bytes[baseOffset + 1] = (byte) ((rgb565 >>> 8) & 0xFF);
|
||||
for (int colorIndex = 0; colorIndex < Math.min(GLYPH_BANK_COLORS_PER_PALETTE, rgbaNode.size()); colorIndex += 1) {
|
||||
final int rgba = rgbaNode.get(colorIndex).asInt(0);
|
||||
final int baseOffset = ((paletteIndex * GLYPH_BANK_COLORS_PER_PALETTE) + colorIndex)
|
||||
* GLYPH_BANK_RGBA8888_BYTES_PER_COLOR;
|
||||
bytes[baseOffset] = (byte) ((rgba >>> 24) & 0xFF);
|
||||
bytes[baseOffset + 1] = (byte) ((rgba >>> 16) & 0xFF);
|
||||
bytes[baseOffset + 2] = (byte) ((rgba >>> 8) & 0xFF);
|
||||
bytes[baseOffset + 3] = (byte) (rgba & 0xFF);
|
||||
}
|
||||
}
|
||||
return bytes;
|
||||
@ -1778,18 +1775,15 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
|
||||
final ArrayNode palettesNode = pipelineNode.putArray("palettes");
|
||||
for (int paletteOrder = 0; paletteOrder < request.selectedPalettes().size(); paletteOrder += 1) {
|
||||
final Map<String, Object> palette = request.selectedPalettes().get(paletteOrder);
|
||||
final List<Integer> originalArgb8888 = integerList(palette.get("originalArgb8888"));
|
||||
final List<Integer> convertedRgb565 = integerList(palette.get("convertedRgb565"));
|
||||
if (originalArgb8888.isEmpty() || convertedRgb565.isEmpty()) {
|
||||
throw new IllegalArgumentException("Each selected palette must contain originalArgb8888 and convertedRgb565 entries.");
|
||||
final List<Integer> rgba8888 = integerList(palette.get("rgba8888"));
|
||||
if (rgba8888.isEmpty()) {
|
||||
throw new IllegalArgumentException("Each selected palette must contain rgba8888 entries.");
|
||||
}
|
||||
final ObjectNode paletteNode = palettesNode.addObject();
|
||||
paletteNode.put("index", paletteIndex(palette, paletteOrder));
|
||||
final ObjectNode payloadNode = paletteNode.putObject("palette");
|
||||
final ArrayNode originalNode = payloadNode.putArray("originalArgb8888");
|
||||
originalArgb8888.forEach(originalNode::add);
|
||||
final ArrayNode convertedNode = payloadNode.putArray("convertedRgb565");
|
||||
convertedRgb565.forEach(convertedNode::add);
|
||||
final ArrayNode rgbaNode = payloadNode.putArray("rgba8888");
|
||||
rgba8888.forEach(rgbaNode::add);
|
||||
}
|
||||
if (palettesNode.isEmpty()) {
|
||||
throw new IllegalArgumentException("At least one palette must be selected.");
|
||||
|
||||
@ -336,17 +336,27 @@ public final class PackerAssetDeclarationParser {
|
||||
continue;
|
||||
}
|
||||
|
||||
final JsonNode originalNode = paletteNode.path("originalArgb8888");
|
||||
final JsonNode convertedNode = paletteNode.path("convertedRgb565");
|
||||
if (!originalNode.isArray() || !convertedNode.isArray()) {
|
||||
final JsonNode rgbaNode = paletteNode.path("rgba8888");
|
||||
if (!rgbaNode.isArray()) {
|
||||
diagnostics.add(new PackerDiagnostic(
|
||||
PackerDiagnosticSeverity.ERROR,
|
||||
PackerDiagnosticCategory.STRUCTURAL,
|
||||
"Each palette payload must contain array fields 'originalArgb8888' and 'convertedRgb565'.",
|
||||
"Each palette payload must contain array field 'rgba8888'.",
|
||||
manifestPath,
|
||||
true));
|
||||
continue;
|
||||
}
|
||||
for (JsonNode colorNode : rgbaNode) {
|
||||
if (!colorNode.canConvertToInt()) {
|
||||
diagnostics.add(new PackerDiagnostic(
|
||||
PackerDiagnosticSeverity.ERROR,
|
||||
PackerDiagnosticCategory.STRUCTURAL,
|
||||
"Palette field 'rgba8888' must contain integer color values.",
|
||||
manifestPath,
|
||||
true));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (paletteCount > MAX_PIPELINE_PALETTES) {
|
||||
|
||||
@ -190,29 +190,20 @@ public final class PackerAssetDetailsService {
|
||||
if (!indexNode.isInt() || indexNode.intValue() < 0 || !(payloadNode instanceof ObjectNode payloadObject)) {
|
||||
return Map.of();
|
||||
}
|
||||
final JsonNode originalNode = payloadObject.path("originalArgb8888");
|
||||
final JsonNode convertedNode = payloadObject.path("convertedRgb565");
|
||||
if (!originalNode.isArray() || !convertedNode.isArray()) {
|
||||
final JsonNode rgbaNode = payloadObject.path("rgba8888");
|
||||
if (!rgbaNode.isArray()) {
|
||||
return Map.of();
|
||||
}
|
||||
final List<Integer> originalArgb8888 = new ArrayList<>();
|
||||
for (JsonNode colorNode : originalNode) {
|
||||
final List<Integer> rgba8888 = new ArrayList<>();
|
||||
for (JsonNode colorNode : rgbaNode) {
|
||||
if (!colorNode.canConvertToInt()) {
|
||||
return Map.of();
|
||||
}
|
||||
originalArgb8888.add(colorNode.intValue());
|
||||
}
|
||||
final List<Integer> convertedRgb565 = new ArrayList<>();
|
||||
for (JsonNode colorNode : convertedNode) {
|
||||
if (!colorNode.canConvertToInt()) {
|
||||
return Map.of();
|
||||
}
|
||||
convertedRgb565.add(colorNode.intValue());
|
||||
rgba8888.add(colorNode.intValue());
|
||||
}
|
||||
return Map.of(
|
||||
"index", indexNode.intValue(),
|
||||
"originalArgb8888", List.copyOf(originalArgb8888),
|
||||
"convertedRgb565", List.copyOf(convertedRgb565));
|
||||
"rgba8888", List.copyOf(rgba8888));
|
||||
}
|
||||
|
||||
private Optional<PackerBankCompositionFile> resolveSelectedBankFile(
|
||||
|
||||
@ -187,7 +187,7 @@ final class PackerGlyphMaterialSupport {
|
||||
try {
|
||||
final int index = Integer.parseInt(entry.getKey());
|
||||
final String color = normalizeHtmlColor(entry.getValue().asText(null));
|
||||
if (index >= 1 && index <= 15 && color != null) {
|
||||
if (index >= 0 && index <= 15 && color != null) {
|
||||
materialIndices.put(index, color);
|
||||
}
|
||||
} catch (NumberFormatException ignored) {
|
||||
@ -197,7 +197,7 @@ final class PackerGlyphMaterialSupport {
|
||||
if (materialIndices.isEmpty()
|
||||
|| materialIndices.size() != materialIndicesNode.size()
|
||||
|| !denseIndices(materialIndices.keySet())) {
|
||||
diagnostics.add(blocking("Schema '" + schemaId + "' must define dense material_indices in range 1..15 using #RRGGBB values.", supportFile));
|
||||
diagnostics.add(blocking("Schema '" + schemaId + "' must define dense material_indices in range 0..15 using #RRGGBB or #RRGGBBAA values.", supportFile));
|
||||
return Optional.empty();
|
||||
}
|
||||
if (!(schemaNode.path("artifact_bindings") instanceof ArrayNode bindingsNode) || bindingsNode.isEmpty()) {
|
||||
@ -242,7 +242,7 @@ final class PackerGlyphMaterialSupport {
|
||||
for (JsonNode colorNode : colorsNode) {
|
||||
final String color = normalizeHtmlColor(colorNode.asText(null));
|
||||
if (color == null) {
|
||||
diagnostics.add(blocking("Palette '" + paletteId + "' must use #RRGGBB colors.", supportFile));
|
||||
diagnostics.add(blocking("Palette '" + paletteId + "' must use #RRGGBB or #RRGGBBAA colors.", supportFile));
|
||||
return Optional.empty();
|
||||
}
|
||||
colors.add(color);
|
||||
@ -260,7 +260,7 @@ final class PackerGlyphMaterialSupport {
|
||||
}
|
||||
|
||||
private boolean denseIndices(Set<Integer> indices) {
|
||||
int expected = 1;
|
||||
int expected = 0;
|
||||
for (Integer index : new TreeSet<>(indices)) {
|
||||
if (index == null || index != expected) {
|
||||
return false;
|
||||
@ -275,7 +275,7 @@ final class PackerGlyphMaterialSupport {
|
||||
return null;
|
||||
}
|
||||
final String normalized = value.trim().toUpperCase();
|
||||
return normalized.matches("^#[0-9A-F]{6}$") ? normalized : null;
|
||||
return normalized.matches("^#[0-9A-F]{6}([0-9A-F]{2})?$") ? normalized : null;
|
||||
}
|
||||
|
||||
private record GlyphMaterialFile(
|
||||
|
||||
@ -109,17 +109,12 @@ public final class PackerReadMessageMapper {
|
||||
"paletteIndices", List.copyOf(paletteIndices));
|
||||
}
|
||||
if (value instanceof PackerPaletteV1 palette) {
|
||||
final List<Integer> originalArgb8888 = new java.util.ArrayList<>(palette.originalArgb8888().length);
|
||||
for (int argb : palette.originalArgb8888()) {
|
||||
originalArgb8888.add(argb);
|
||||
}
|
||||
final List<Integer> convertedRgb565 = new java.util.ArrayList<>(palette.convertedRgb565().length);
|
||||
for (short rgb565 : palette.convertedRgb565()) {
|
||||
convertedRgb565.add(Short.toUnsignedInt(rgb565));
|
||||
final List<Integer> rgba8888 = new java.util.ArrayList<>(palette.rgba8888().length);
|
||||
for (int rgba : palette.rgba8888()) {
|
||||
rgba8888.add(rgba);
|
||||
}
|
||||
return Map.of(
|
||||
"originalArgb8888", List.copyOf(originalArgb8888),
|
||||
"convertedRgb565", List.copyOf(convertedRgb565));
|
||||
"rgba8888", List.copyOf(rgba8888));
|
||||
}
|
||||
if (value instanceof Map<?, ?> nestedMap) {
|
||||
final java.util.LinkedHashMap<String, Object> normalized = new java.util.LinkedHashMap<>();
|
||||
|
||||
@ -5,8 +5,10 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import p.packer.models.PackerAssetCacheEntry;
|
||||
import p.packer.models.PackerFileCacheEntry;
|
||||
import p.packer.models.PackerTileIndexedV1;
|
||||
import p.packer.models.PackerDiagnostic;
|
||||
import p.packer.models.PackerGlyphBankRequirements;
|
||||
import p.packer.models.PackerPaletteV1;
|
||||
import p.packer.models.PackerTileIndexedV1;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
@ -42,7 +44,7 @@ final class PackerGlyphBankWalkerTest {
|
||||
null,
|
||||
Map.of(
|
||||
"tile", Map.of("width", 16, "height", 16),
|
||||
"palette", Map.of("originalArgb8888", List.of(0xFFFFFFFF))),
|
||||
"palette", Map.of("rgba8888", List.of(0xFFFFFFFF))),
|
||||
List.of())));
|
||||
|
||||
final var result = walker.walk(assetRoot, new PackerGlyphBankRequirements(16), Optional.of(priorCache));
|
||||
@ -52,6 +54,54 @@ final class PackerGlyphBankWalkerTest {
|
||||
assertTrue(tile.paletteIndices().length > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void preservesRgba8888PaletteDataAndUsesIndexZeroAsOrdinaryColor() throws Exception {
|
||||
final Path assetRoot = Files.createDirectories(tempDir.resolve("rgba"));
|
||||
final Path filePath = assetRoot.resolve("tile.png");
|
||||
final BufferedImage image = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
|
||||
image.setRGB(0, 0, 0x80FF0000);
|
||||
image.setRGB(1, 0, 0x00000000);
|
||||
image.setRGB(2, 0, 0xFFFF00FF);
|
||||
for (int y = 0; y < 16; y++) {
|
||||
for (int x = 3; x < 16; x++) {
|
||||
image.setRGB(x, y, 0xFF00FF00);
|
||||
}
|
||||
}
|
||||
ImageIO.write(image, "png", filePath.toFile());
|
||||
|
||||
final PackerGlyphBankWalker walker = new PackerGlyphBankWalker(MAPPER);
|
||||
final var result = walker.walk(assetRoot, new PackerGlyphBankRequirements(16));
|
||||
|
||||
assertTrue(result.diagnostics().stream().noneMatch(diagnostic -> diagnostic.message().contains("partial alpha")));
|
||||
final var metadata = result.probeResults().getFirst().metadata();
|
||||
final PackerTileIndexedV1 tile = (PackerTileIndexedV1) metadata.get("tile");
|
||||
final PackerPaletteV1 palette = (PackerPaletteV1) metadata.get("palette");
|
||||
|
||||
assertEquals(0, Byte.toUnsignedInt(tile.paletteIndices()[0]));
|
||||
assertEquals(1, Byte.toUnsignedInt(tile.paletteIndices()[1]));
|
||||
assertEquals(2, Byte.toUnsignedInt(tile.paletteIndices()[2]));
|
||||
assertArrayEquals(new int[]{0xFF000080, 0x00000000, 0xFF00FFFF, 0x00FF00FF}, palette.rgba8888());
|
||||
}
|
||||
|
||||
@Test
|
||||
void acceptsSixteenRgbaColorsAndRejectsSeventeenth() throws Exception {
|
||||
final Path validRoot = Files.createDirectories(tempDir.resolve("sixteen"));
|
||||
writeColorCountTile(validRoot.resolve("tile.png"), 16);
|
||||
|
||||
final PackerGlyphBankWalker walker = new PackerGlyphBankWalker(MAPPER);
|
||||
final var valid = walker.walk(validRoot, new PackerGlyphBankRequirements(16));
|
||||
|
||||
assertTrue(valid.diagnostics().stream().noneMatch(PackerDiagnostic::blocking));
|
||||
|
||||
final Path invalidRoot = Files.createDirectories(tempDir.resolve("seventeen"));
|
||||
writeColorCountTile(invalidRoot.resolve("tile.png"), 17);
|
||||
final var invalid = walker.walk(invalidRoot, new PackerGlyphBankRequirements(16));
|
||||
|
||||
assertTrue(invalid.probeResults().stream()
|
||||
.flatMap(probeResult -> probeResult.diagnostics().stream())
|
||||
.anyMatch(PackerDiagnostic::blocking));
|
||||
}
|
||||
|
||||
private void writeTile(Path path) throws Exception {
|
||||
final BufferedImage image = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
|
||||
for (int y = 0; y < 16; y++) {
|
||||
@ -61,4 +111,18 @@ final class PackerGlyphBankWalkerTest {
|
||||
}
|
||||
ImageIO.write(image, "png", path.toFile());
|
||||
}
|
||||
|
||||
private void writeColorCountTile(Path path, int colorCount) throws Exception {
|
||||
final BufferedImage image = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
|
||||
for (int y = 0; y < 16; y++) {
|
||||
for (int x = 0; x < 16; x++) {
|
||||
final int index = Math.min(colorCount - 1, y * 16 + x);
|
||||
final int red = (index * 17) & 0xFF;
|
||||
final int green = (index * 31) & 0xFF;
|
||||
final int blue = (index * 47) & 0xFF;
|
||||
image.setRGB(x, y, 0xFF000000 | (red << 16) | (green << 8) | blue);
|
||||
}
|
||||
}
|
||||
ImageIO.write(image, "png", path.toFile());
|
||||
}
|
||||
}
|
||||
|
||||
@ -250,8 +250,7 @@ final class PackerRuntimeAssetMaterializerTest {
|
||||
|
||||
private static Map<String, JsonNode> palettePipeline() {
|
||||
final var payload = MAPPER.createObjectNode();
|
||||
payload.putArray("originalArgb8888").add(0xFFFF0000);
|
||||
payload.putArray("convertedRgb565").add(0xF800);
|
||||
payload.putArray("rgba8888").add(0xFF0000FF);
|
||||
final var declaration = MAPPER.createObjectNode();
|
||||
declaration.put("index", 0);
|
||||
declaration.set("palette", payload);
|
||||
|
||||
@ -295,9 +295,7 @@ final class FileSystemPackerWorkspaceServiceTest {
|
||||
final ObjectNode palette = palettes.addObject();
|
||||
palette.put("index", 0);
|
||||
palette.putObject("palette")
|
||||
.putArray("originalArgb8888").add(0xFFFF0000);
|
||||
((ObjectNode) palette.path("palette"))
|
||||
.putArray("convertedRgb565").add(0xF800);
|
||||
.putArray("rgba8888").add(0xFF0000FF);
|
||||
final var artifacts = manifest.putArray("artifacts");
|
||||
artifacts.addObject().put("file", "confirm.png").put("index", 0);
|
||||
MAPPER.writerWithDefaultPrettyPrinter().writeValue(manifestPath.toFile(), manifest);
|
||||
@ -330,8 +328,10 @@ final class FileSystemPackerWorkspaceServiceTest {
|
||||
final int packedPixelBytes = (assetTable.get(0).path("metadata").path("width").asInt()
|
||||
* assetTable.get(0).path("metadata").path("height").asInt()) / 2;
|
||||
final int paletteStart = payloadOffset + assetOffset + packedPixelBytes;
|
||||
assertEquals(0xF81F, readLeUnsignedShort(assetsPa, paletteStart));
|
||||
assertEquals(0xF800, readLeUnsignedShort(assetsPa, paletteStart + 2));
|
||||
assertEquals(0xFF, assetsPa[paletteStart] & 0xFF);
|
||||
assertEquals(0x00, assetsPa[paletteStart + 1] & 0xFF);
|
||||
assertEquals(0x00, assetsPa[paletteStart + 2] & 0xFF);
|
||||
assertEquals(0xFF, assetsPa[paletteStart + 3] & 0xFF);
|
||||
assertEquals(1, assetTable.get(0).path("asset_id").asInt());
|
||||
assertEquals("GLYPH", assetTable.get(0).path("bank_type").asText());
|
||||
assertEquals("NONE", assetTable.get(0).path("codec").asText());
|
||||
@ -369,9 +369,7 @@ final class FileSystemPackerWorkspaceServiceTest {
|
||||
final ObjectNode palette = palettes.addObject();
|
||||
palette.put("index", 0);
|
||||
palette.putObject("palette")
|
||||
.putArray("originalArgb8888").add(0xFF00FF00).add(0xFFFF0000);
|
||||
((ObjectNode) palette.path("palette"))
|
||||
.putArray("convertedRgb565").add(0x07E0).add(0xF800);
|
||||
.putArray("rgba8888").add(0x00FF00FF).add(0xFF0000FF);
|
||||
final var artifacts = manifest.putArray("artifacts");
|
||||
artifacts.addObject()
|
||||
.put("artifact_id", "art-confirm")
|
||||
@ -387,8 +385,8 @@ final class FileSystemPackerWorkspaceServiceTest {
|
||||
"status": "ok",
|
||||
"version": 1,
|
||||
"material_indices": {
|
||||
"1": "#00FF00",
|
||||
"2": "#FF0000"
|
||||
"0": "#00FF00",
|
||||
"1": "#FF0000"
|
||||
},
|
||||
"artifact_bindings": [
|
||||
{
|
||||
@ -417,7 +415,7 @@ final class FileSystemPackerWorkspaceServiceTest {
|
||||
final int payloadOffset = (int) readLeLong(assetsPa, 12);
|
||||
final var assetTable = MAPPER.readTree(projectRoot.resolve("build/asset_table.json").toFile());
|
||||
final int assetOffset = assetTable.get(0).path("offset").asInt();
|
||||
assertEquals(0x21, assetsPa[payloadOffset + assetOffset] & 0xFF);
|
||||
assertEquals(0x10, assetsPa[payloadOffset + assetOffset] & 0xFF);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -436,9 +434,7 @@ final class FileSystemPackerWorkspaceServiceTest {
|
||||
final ObjectNode palette = palettes.addObject();
|
||||
palette.put("index", 0);
|
||||
palette.putObject("palette")
|
||||
.putArray("originalArgb8888").add(0xFFFF0000);
|
||||
((ObjectNode) palette.path("palette"))
|
||||
.putArray("convertedRgb565").add(0xF800);
|
||||
.putArray("rgba8888").add(0xFF0000FF);
|
||||
final var artifacts = manifest.putArray("artifacts");
|
||||
artifacts.addObject().put("file", "selected.png").put("index", 0);
|
||||
MAPPER.writerWithDefaultPrettyPrinter().writeValue(manifestPath.toFile(), manifest);
|
||||
@ -502,9 +498,7 @@ final class FileSystemPackerWorkspaceServiceTest {
|
||||
final ObjectNode palette = palettes.addObject();
|
||||
palette.put("index", 0);
|
||||
palette.putObject("palette")
|
||||
.putArray("originalArgb8888").add(0xFFFF0000);
|
||||
((ObjectNode) palette.path("palette"))
|
||||
.putArray("convertedRgb565").add(0xF800);
|
||||
.putArray("rgba8888").add(0xFF0000FF);
|
||||
final var artifacts = manifest.putArray("artifacts");
|
||||
artifacts.addObject().put("file", "oversized.png").put("index", 0);
|
||||
MAPPER.writerWithDefaultPrettyPrinter().writeValue(manifestPath.toFile(), manifest);
|
||||
@ -533,9 +527,7 @@ final class FileSystemPackerWorkspaceServiceTest {
|
||||
final ObjectNode palette = palettes.addObject();
|
||||
palette.put("index", 0);
|
||||
palette.putObject("palette")
|
||||
.putArray("originalArgb8888").add(0xFFFF0000);
|
||||
((ObjectNode) palette.path("palette"))
|
||||
.putArray("convertedRgb565").add(0xF800);
|
||||
.putArray("rgba8888").add(0xFF0000FF);
|
||||
final var artifacts = manifest.putArray("artifacts");
|
||||
artifacts.addObject().put("file", "checker.png").put("index", 0);
|
||||
MAPPER.writerWithDefaultPrettyPrinter().writeValue(manifestPath.toFile(), manifest);
|
||||
@ -1016,8 +1008,7 @@ final class FileSystemPackerWorkspaceServiceTest {
|
||||
project(projectRoot),
|
||||
AssetReference.forAssetId(1),
|
||||
List.of(Map.of(
|
||||
"originalArgb8888", List.of(0xFFFF0000),
|
||||
"convertedRgb565", List.of(0xF800))))).success());
|
||||
"rgba8888", List.of(0xFF0000FF))))).success());
|
||||
|
||||
final var manifest = MAPPER.readTree(assetRoot.resolve("asset.json").toFile());
|
||||
final var selectedArtifact = manifest.path("artifacts").get(0);
|
||||
@ -1031,7 +1022,7 @@ final class FileSystemPackerWorkspaceServiceTest {
|
||||
"schema_id": "schema-1",
|
||||
"status": "ok",
|
||||
"version": 2,
|
||||
"material_indices": { "1": "#FF0000" },
|
||||
"material_indices": { "0": "#FF0000" },
|
||||
"artifact_bindings": [
|
||||
{ "artifact_id": "%s", "fingerprint": "%s" }
|
||||
]
|
||||
@ -1074,8 +1065,7 @@ final class FileSystemPackerWorkspaceServiceTest {
|
||||
project(projectRoot),
|
||||
AssetReference.forAssetId(1),
|
||||
List.of(Map.of(
|
||||
"originalArgb8888", List.of(0xFFFF0000),
|
||||
"convertedRgb565", List.of(0xF800))))).success());
|
||||
"rgba8888", List.of(0xFF0000FF))))).success());
|
||||
|
||||
final var manifest = MAPPER.readTree(assetRoot.resolve("asset.json").toFile());
|
||||
final var selectedArtifact = manifest.path("artifacts").get(0);
|
||||
@ -1089,7 +1079,7 @@ final class FileSystemPackerWorkspaceServiceTest {
|
||||
"schema_id": "schema-1",
|
||||
"status": "ok",
|
||||
"version": 1,
|
||||
"material_indices": { "1": "#FF0000" },
|
||||
"material_indices": { "0": "#FF0000" },
|
||||
"artifact_bindings": [
|
||||
{ "artifact_id": "%s", "fingerprint": "%s" }
|
||||
]
|
||||
@ -1129,20 +1119,18 @@ final class FileSystemPackerWorkspaceServiceTest {
|
||||
AssetReference.forAssetId(1),
|
||||
List.of(
|
||||
Map.of(
|
||||
"originalArgb8888", List.of(0xFFFF0000, 0xFF00FF00),
|
||||
"convertedRgb565", List.of(0xF800, 0x07E0)),
|
||||
"rgba8888", List.of(0xFF0000FF, 0x00FF00FF)),
|
||||
Map.of(
|
||||
"originalArgb8888", List.of(0xFF0000FF),
|
||||
"convertedRgb565", List.of(0x001F)))));
|
||||
"rgba8888", List.of(0x0000FFFF)))));
|
||||
|
||||
assertTrue(response.success());
|
||||
|
||||
final var manifest = MAPPER.readTree(projectRoot.resolve("assets/ui/atlas/asset.json").toFile());
|
||||
assertEquals(2, manifest.path("output").path("pipeline").path("palettes").size());
|
||||
assertEquals(0, manifest.path("output").path("pipeline").path("palettes").get(0).path("index").asInt());
|
||||
assertEquals(0xFFFF0000, manifest.path("output").path("pipeline").path("palettes").get(0).path("palette").path("originalArgb8888").get(0).asInt());
|
||||
assertEquals(0xFF0000FF, manifest.path("output").path("pipeline").path("palettes").get(0).path("palette").path("rgba8888").get(0).asInt());
|
||||
assertEquals(1, manifest.path("output").path("pipeline").path("palettes").get(1).path("index").asInt());
|
||||
assertEquals(0x001F, manifest.path("output").path("pipeline").path("palettes").get(1).path("palette").path("convertedRgb565").get(0).asInt());
|
||||
assertEquals(0x0000FFFF, manifest.path("output").path("pipeline").path("palettes").get(1).path("palette").path("rgba8888").get(0).asInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@ -158,8 +158,7 @@ final class PackerAssetDeclarationParserTest {
|
||||
{
|
||||
"index": 1,
|
||||
"palette": {
|
||||
"originalArgb8888": [4294901760],
|
||||
"convertedRgb565": [63488]
|
||||
"rgba8888": [-16776961]
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -174,7 +173,7 @@ final class PackerAssetDeclarationParserTest {
|
||||
assertTrue(result.valid());
|
||||
assertTrue(result.declaration().outputPipelineMetadata().get("palettes").isArray());
|
||||
assertEquals(1, result.declaration().outputPipelineMetadata().get("palettes").get(0).path("index").asInt());
|
||||
assertEquals(63488, result.declaration().outputPipelineMetadata().get("palettes").get(0).path("palette").path("convertedRgb565").get(0).asInt());
|
||||
assertEquals(0xFF0000FF, result.declaration().outputPipelineMetadata().get("palettes").get(0).path("palette").path("rgba8888").get(0).asInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -194,15 +193,13 @@ final class PackerAssetDeclarationParserTest {
|
||||
{
|
||||
"index": 0,
|
||||
"palette": {
|
||||
"originalArgb8888": [4294901760],
|
||||
"convertedRgb565": [63488]
|
||||
"rgba8888": [-16776961]
|
||||
}
|
||||
},
|
||||
{
|
||||
"index": 0,
|
||||
"palette": {
|
||||
"originalArgb8888": [4278255360],
|
||||
"convertedRgb565": [2016]
|
||||
"rgba8888": [16711935]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@ -66,9 +66,7 @@ final class PackerAssetDetailsServiceTest {
|
||||
final ObjectNode declaration = palettes.addObject();
|
||||
declaration.put("index", 3);
|
||||
declaration.putObject("palette")
|
||||
.putArray("originalArgb8888").add(0xFFFF0000).add(0xFF00FF00);
|
||||
((ObjectNode) declaration.path("palette"))
|
||||
.putArray("convertedRgb565").add(0xF800).add(0x07E0);
|
||||
.putArray("rgba8888").add(0xFF0000FF).add(0x00FF00FF);
|
||||
mapper.writerWithDefaultPrettyPrinter().writeValue(manifestPath.toFile(), manifest);
|
||||
|
||||
final PackerAssetDetailsService service = service();
|
||||
@ -77,8 +75,8 @@ final class PackerAssetDetailsServiceTest {
|
||||
assertEquals(1, result.details().pipelinePalettes().size());
|
||||
assertEquals(3, result.details().pipelinePalettes().getFirst().get("index"));
|
||||
assertEquals(
|
||||
List.of(0xFFFF0000, 0xFF00FF00),
|
||||
result.details().pipelinePalettes().getFirst().get("originalArgb8888"));
|
||||
List.of(0xFF0000FF, 0x00FF00FF),
|
||||
result.details().pipelinePalettes().getFirst().get("rgba8888"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -202,7 +200,7 @@ final class PackerAssetDetailsServiceTest {
|
||||
final var palette = (Map<?, ?>) selectedFile.metadata().get("palette");
|
||||
assertEquals(16, ((Number) tile.get("width")).intValue());
|
||||
assertEquals(16, ((Number) tile.get("height")).intValue());
|
||||
assertFalse(((List<?>) palette.get("originalArgb8888")).isEmpty());
|
||||
assertFalse(((List<?>) palette.get("rgba8888")).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@ -337,12 +337,12 @@ public final class AssetDetailsPaletteOverhaulingCoordinator {
|
||||
final List<Integer> argb = record.colors().stream()
|
||||
.map(AssetDetailsPaletteOverhaulingCoordinator::htmlToArgb8888)
|
||||
.toList();
|
||||
final List<Integer> rgb565 = record.colors().stream()
|
||||
.map(AssetDetailsPaletteOverhaulingCoordinator::htmlToRgb565)
|
||||
final List<Integer> rgba = record.colors().stream()
|
||||
.map(AssetDetailsPaletteOverhaulingCoordinator::htmlToRgba8888)
|
||||
.toList();
|
||||
final Map<String, Object> palette = Map.of(
|
||||
"originalArgb8888", argb,
|
||||
"convertedRgb565", rgb565);
|
||||
"rgba8888", rgba);
|
||||
final Map<String, Object> metadata = new LinkedHashMap<>();
|
||||
metadata.put("palette", palette);
|
||||
metadata.put("paletteId", record.paletteId());
|
||||
@ -452,7 +452,7 @@ public final class AssetDetailsPaletteOverhaulingCoordinator {
|
||||
}
|
||||
|
||||
private static ColorSample toColorSample(String htmlColor) {
|
||||
final int rgb = Integer.parseInt(normalizeHtmlColor(htmlColor).substring(1), 16);
|
||||
final int rgb = Integer.parseInt(normalizeHtmlColor(htmlColor).substring(1, 7), 16);
|
||||
final double red = ((rgb >> 16) & 0xFF) / 255.0;
|
||||
final double green = ((rgb >> 8) & 0xFF) / 255.0;
|
||||
final double blue = (rgb & 0xFF) / 255.0;
|
||||
@ -504,25 +504,38 @@ public final class AssetDetailsPaletteOverhaulingCoordinator {
|
||||
if (left == null || right == null) {
|
||||
return false;
|
||||
}
|
||||
return Objects.equals(left.get("originalArgb8888"), right.get("originalArgb8888"))
|
||||
&& Objects.equals(left.get("convertedRgb565"), right.get("convertedRgb565"));
|
||||
final Object leftRgba = left.get("rgba8888");
|
||||
final Object rightRgba = right.get("rgba8888");
|
||||
if (leftRgba != null || rightRgba != null) {
|
||||
return Objects.equals(leftRgba, rightRgba);
|
||||
}
|
||||
return Objects.equals(left.get("originalArgb8888"), right.get("originalArgb8888"));
|
||||
}
|
||||
|
||||
private static int htmlToArgb8888(String htmlColor) {
|
||||
final int rgb = Integer.parseInt(normalizeHtmlColor(htmlColor).substring(1), 16);
|
||||
return 0xFF000000 | rgb;
|
||||
final String normalized = normalizeHtmlColor(htmlColor);
|
||||
final int rgb = Integer.parseInt(normalized.substring(1, 7), 16);
|
||||
final int alpha = normalized.length() == 9
|
||||
? Integer.parseInt(normalized.substring(7, 9), 16)
|
||||
: 0xFF;
|
||||
return (alpha << 24) | rgb;
|
||||
}
|
||||
|
||||
private static int htmlToRgb565(String htmlColor) {
|
||||
final int rgb = Integer.parseInt(normalizeHtmlColor(htmlColor).substring(1), 16);
|
||||
final int red = (rgb >> 16) & 0xFF;
|
||||
final int green = (rgb >> 8) & 0xFF;
|
||||
final int blue = rgb & 0xFF;
|
||||
return ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);
|
||||
private static int htmlToRgba8888(String htmlColor) {
|
||||
final String normalized = normalizeHtmlColor(htmlColor);
|
||||
final int rgb = Integer.parseInt(normalized.substring(1, 7), 16);
|
||||
final int alpha = normalized.length() == 9
|
||||
? Integer.parseInt(normalized.substring(7, 9), 16)
|
||||
: 0xFF;
|
||||
return (rgb << 8) | alpha;
|
||||
}
|
||||
|
||||
private static String normalizeHtmlColor(String color) {
|
||||
return Objects.requireNonNull(color, "color").trim().toUpperCase();
|
||||
final String normalized = Objects.requireNonNull(color, "color").trim().toUpperCase();
|
||||
if (!normalized.matches("^#[0-9A-F]{6}([0-9A-F]{2})?$")) {
|
||||
throw new IllegalArgumentException("color must use #RRGGBB or #RRGGBBAA format");
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private static AssetWorkspaceBankCompositionFile fileByPath(List<AssetWorkspaceBankCompositionFile> files, String path) {
|
||||
|
||||
@ -45,7 +45,7 @@ public final class AssetDetailsPaletteOverhaulingDualListView extends StudioDual
|
||||
final HBox strip = new HBox(0);
|
||||
strip.getStyleClass().add("assets-details-palette-strip");
|
||||
final List<Integer> colors = paletteDisplayColors(item.metadata());
|
||||
for (int index = 0; index < 15; index++) {
|
||||
for (int index = 0; index < 16; index++) {
|
||||
final Region swatch = new Region();
|
||||
swatch.getStyleClass().add("assets-details-palette-swatch");
|
||||
swatch.setMinSize(12.0d, 18.0d);
|
||||
@ -63,10 +63,10 @@ public final class AssetDetailsPaletteOverhaulingDualListView extends StudioDual
|
||||
if (!(paletteValue instanceof Map<?, ?> palette)) {
|
||||
return List.of();
|
||||
}
|
||||
final Object convertedValue = palette.get("convertedRgb565");
|
||||
final Object convertedValue = palette.get("rgba8888");
|
||||
if (convertedValue instanceof List<?> converted) {
|
||||
return ((List<Integer>) converted).stream()
|
||||
.map(this::rgb565ToArgb8888)
|
||||
.map(this::rgba8888ToArgb8888)
|
||||
.toList();
|
||||
}
|
||||
return List.of();
|
||||
@ -91,14 +91,15 @@ public final class AssetDetailsPaletteOverhaulingDualListView extends StudioDual
|
||||
return String.format("#%02X%02X%02X", red, green, blue);
|
||||
}
|
||||
|
||||
private int rgb565ToArgb8888(Integer rgb565) {
|
||||
if (rgb565 == null) {
|
||||
private int rgba8888ToArgb8888(Integer rgba8888) {
|
||||
if (rgba8888 == null) {
|
||||
return 0x00000000;
|
||||
}
|
||||
final int value = rgb565 & 0xFFFF;
|
||||
final int red = ((value >> 11) & 0x1F) * 255 / 31;
|
||||
final int green = ((value >> 5) & 0x3F) * 255 / 63;
|
||||
final int blue = (value & 0x1F) * 255 / 31;
|
||||
return 0xFF000000 | (red << 16) | (green << 8) | blue;
|
||||
final int value = rgba8888;
|
||||
final int red = (value >>> 24) & 0xFF;
|
||||
final int green = (value >>> 16) & 0xFF;
|
||||
final int blue = (value >>> 8) & 0xFF;
|
||||
final int alpha = value & 0xFF;
|
||||
return (alpha << 24) | (red << 16) | (green << 8) | blue;
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,7 +88,7 @@ public final class AssetDetailsPaletteOverhaulingPreviewPane extends VBox {
|
||||
paletteStrip.setMinWidth(PREVIEW_FRAME_SIZE);
|
||||
paletteStrip.setPrefWidth(PREVIEW_FRAME_SIZE);
|
||||
paletteStrip.setMaxWidth(PREVIEW_FRAME_SIZE);
|
||||
for (int index = 0; index < 15; index++) {
|
||||
for (int index = 0; index < 16; index++) {
|
||||
final Region swatch = new Region();
|
||||
swatch.getStyleClass().add("assets-details-palette-preview-swatch");
|
||||
swatch.setMinSize(16.0d, 26.0d);
|
||||
@ -199,10 +199,10 @@ public final class AssetDetailsPaletteOverhaulingPreviewPane extends VBox {
|
||||
if (palette == null) {
|
||||
return List.of();
|
||||
}
|
||||
final Object converted = palette.get("convertedRgb565");
|
||||
final Object converted = palette.get("rgba8888");
|
||||
if (converted instanceof List<?> list) {
|
||||
return ((List<Integer>) list).stream()
|
||||
.map(this::rgb565ToArgb8888)
|
||||
.map(this::rgba8888ToArgb8888)
|
||||
.toList();
|
||||
}
|
||||
return List.of();
|
||||
@ -214,6 +214,12 @@ public final class AssetDetailsPaletteOverhaulingPreviewPane extends VBox {
|
||||
if (palette == null) {
|
||||
return List.of();
|
||||
}
|
||||
final Object rgba = palette.get("rgba8888");
|
||||
if (rgba instanceof List<?> list) {
|
||||
return ((List<Integer>) list).stream()
|
||||
.map(this::rgba8888ToArgb8888)
|
||||
.toList();
|
||||
}
|
||||
final Object original = palette.get("originalArgb8888");
|
||||
if (original instanceof List<?> list) {
|
||||
return (List<Integer>) list;
|
||||
@ -229,7 +235,7 @@ public final class AssetDetailsPaletteOverhaulingPreviewPane extends VBox {
|
||||
final Map<String, Integer> indicesByColor = new java.util.LinkedHashMap<>();
|
||||
for (Map.Entry<?, ?> entry : map.entrySet()) {
|
||||
final Integer index = asInteger(entry.getKey());
|
||||
if (index == null || index <= 0 || !(entry.getValue() instanceof String color) || color.isBlank()) {
|
||||
if (index == null || index < 0 || !(entry.getValue() instanceof String color) || color.isBlank()) {
|
||||
continue;
|
||||
}
|
||||
indicesByColor.put(color.trim().toUpperCase(), index);
|
||||
@ -245,21 +251,20 @@ public final class AssetDetailsPaletteOverhaulingPreviewPane extends VBox {
|
||||
}
|
||||
|
||||
private int paletteIndexToArgb(List<Integer> colors, int paletteIndex) {
|
||||
if (paletteIndex <= 0) {
|
||||
if (paletteIndex < 0) {
|
||||
return 0x00000000;
|
||||
}
|
||||
return colorAt(colors, paletteIndex - 1);
|
||||
return colorAt(colors, paletteIndex);
|
||||
}
|
||||
|
||||
private int schemaPaletteIndex(int tilePaletteIndex, List<Integer> sourceColors, Map<String, Integer> schemaIndicesByColor) {
|
||||
if (tilePaletteIndex <= 0 || sourceColors.isEmpty() || schemaIndicesByColor.isEmpty()) {
|
||||
if (tilePaletteIndex < 0 || sourceColors.isEmpty() || schemaIndicesByColor.isEmpty()) {
|
||||
return tilePaletteIndex;
|
||||
}
|
||||
final int sourceColorIndex = tilePaletteIndex - 1;
|
||||
if (sourceColorIndex < 0 || sourceColorIndex >= sourceColors.size()) {
|
||||
if (tilePaletteIndex >= sourceColors.size()) {
|
||||
return 0;
|
||||
}
|
||||
return schemaIndicesByColor.getOrDefault(cssColor(sourceColors.get(sourceColorIndex)), 0);
|
||||
return schemaIndicesByColor.getOrDefault(materialColorKey(sourceColors.get(tilePaletteIndex)), 0);
|
||||
}
|
||||
|
||||
private int colorAt(List<Integer> colors, int index) {
|
||||
@ -281,6 +286,16 @@ public final class AssetDetailsPaletteOverhaulingPreviewPane extends VBox {
|
||||
return String.format("#%02X%02X%02X", red, green, blue);
|
||||
}
|
||||
|
||||
private String materialColorKey(int argb) {
|
||||
final int alpha = (argb >>> 24) & 0xFF;
|
||||
final int red = (argb >>> 16) & 0xFF;
|
||||
final int green = (argb >>> 8) & 0xFF;
|
||||
final int blue = argb & 0xFF;
|
||||
return alpha == 0xFF
|
||||
? String.format("#%02X%02X%02X", red, green, blue)
|
||||
: String.format("#%02X%02X%02X%02X", red, green, blue, alpha);
|
||||
}
|
||||
|
||||
private javafx.scene.paint.Color toFxColor(int argb) {
|
||||
final int alpha = (argb >>> 24) & 0xFF;
|
||||
if (alpha == 0) {
|
||||
@ -292,14 +307,15 @@ public final class AssetDetailsPaletteOverhaulingPreviewPane extends VBox {
|
||||
return javafx.scene.paint.Color.rgb(red, green, blue, alpha / 255.0d);
|
||||
}
|
||||
|
||||
private int rgb565ToArgb8888(Integer rgb565) {
|
||||
if (rgb565 == null) {
|
||||
private int rgba8888ToArgb8888(Integer rgba8888) {
|
||||
if (rgba8888 == null) {
|
||||
return 0x00000000;
|
||||
}
|
||||
final int value = rgb565 & 0xFFFF;
|
||||
final int red = ((value >> 11) & 0x1F) * 255 / 31;
|
||||
final int green = ((value >> 5) & 0x3F) * 255 / 63;
|
||||
final int blue = (value & 0x1F) * 255 / 31;
|
||||
return 0xFF000000 | (red << 16) | (green << 8) | blue;
|
||||
final int value = rgba8888;
|
||||
final int red = (value >>> 24) & 0xFF;
|
||||
final int green = (value >>> 16) & 0xFF;
|
||||
final int blue = (value >>> 8) & 0xFF;
|
||||
final int alpha = value & 0xFF;
|
||||
return (alpha << 24) | (red << 16) | (green << 8) | blue;
|
||||
}
|
||||
}
|
||||
|
||||
@ -239,7 +239,7 @@ public final class AssetDetailsPaletteWizardDialog {
|
||||
editableStrip.getChildren().setAll(editableColors.stream().map(this::swatch).toList());
|
||||
nameField.setPromptText(AssetDetailsPaletteOverhaulingCoordinator.generatedDisplayName(editableColors));
|
||||
indexItems.setAll(java.util.stream.IntStream.range(0, editableColors.size())
|
||||
.mapToObj(index -> (index + 1) + " -> " + editableColors.get(index))
|
||||
.mapToObj(index -> index + " -> " + editableColors.get(index))
|
||||
.toList());
|
||||
final boolean duplicate = schema != null && coordinator.duplicatePaletteExists(paletteId, schema.schemaId(), editableColors);
|
||||
summaryLabel.setText(duplicate
|
||||
|
||||
@ -164,7 +164,7 @@ public final class AssetDetailsColorSchemaCoordinator {
|
||||
List<String> orderedColors) {
|
||||
final Map<Integer, String> materialIndices = new LinkedHashMap<>();
|
||||
for (int index = 0; index < orderedColors.size(); index += 1) {
|
||||
materialIndices.put(index + 1, normalizeHtmlColor(orderedColors.get(index)));
|
||||
materialIndices.put(index, normalizeHtmlColor(orderedColors.get(index)));
|
||||
}
|
||||
final List<AssetStudioGlyphMaterialArtifactBinding> bindings = assignedTiles.stream()
|
||||
.map(file -> new AssetStudioGlyphMaterialArtifactBinding(artifactId(file), fingerprint(file)))
|
||||
@ -217,12 +217,35 @@ public final class AssetDetailsColorSchemaCoordinator {
|
||||
if (palette == null) {
|
||||
return List.of();
|
||||
}
|
||||
final Object originalColors = palette.get("originalArgb8888");
|
||||
if (!(originalColors instanceof Iterable<?> iterable)) {
|
||||
return List.of();
|
||||
}
|
||||
final List<String> colors = new ArrayList<>();
|
||||
for (Object item : iterable) {
|
||||
final Object rgbaColors = palette.get("rgba8888");
|
||||
if (rgbaColors instanceof Iterable<?> rgbaIterable) {
|
||||
for (Object item : rgbaIterable) {
|
||||
if (!(item instanceof Number number)) {
|
||||
continue;
|
||||
}
|
||||
final int value = number.intValue();
|
||||
final int alpha = value & 0xFF;
|
||||
colors.add(alpha == 0xFF
|
||||
? String.format(
|
||||
"#%02X%02X%02X",
|
||||
(value >>> 24) & 0xFF,
|
||||
(value >>> 16) & 0xFF,
|
||||
(value >>> 8) & 0xFF)
|
||||
: String.format(
|
||||
"#%02X%02X%02X%02X",
|
||||
(value >>> 24) & 0xFF,
|
||||
(value >>> 16) & 0xFF,
|
||||
(value >>> 8) & 0xFF,
|
||||
alpha));
|
||||
}
|
||||
if (!colors.isEmpty()) {
|
||||
return List.copyOf(colors);
|
||||
}
|
||||
}
|
||||
final Object originalColors = palette.get("originalArgb8888");
|
||||
if (originalColors instanceof Iterable<?> argbIterable) {
|
||||
for (Object item : argbIterable) {
|
||||
if (!(item instanceof Number number)) {
|
||||
continue;
|
||||
}
|
||||
@ -232,6 +255,7 @@ public final class AssetDetailsColorSchemaCoordinator {
|
||||
(number.intValue() >> 8) & 0xFF,
|
||||
number.intValue() & 0xFF));
|
||||
}
|
||||
}
|
||||
return List.copyOf(colors);
|
||||
}
|
||||
|
||||
|
||||
@ -193,7 +193,7 @@ public final class AssetDetailsColorSchemaGroupsControl extends StudioFormSectio
|
||||
workspaceBus.publish(new StudioAssetLogEvent("color-schema-groups", "Apply failed: schema must contain at least one tile."));
|
||||
return;
|
||||
}
|
||||
if (orderedColors.size() > 15) {
|
||||
if (orderedColors.size() > 16) {
|
||||
workspaceBus.publish(new StudioAssetLogEvent(
|
||||
"color-schema-groups",
|
||||
Container.i18n().text(I18n.ASSETS_COLOR_SCHEMA_DIALOG_TOO_MANY_COLORS)));
|
||||
@ -326,7 +326,7 @@ public final class AssetDetailsColorSchemaGroupsControl extends StudioFormSectio
|
||||
final List<String> orderedColors = draft.orderedColors();
|
||||
schemaTitleLabel.setText(draft.schemaId() + (draft.existingSchema() ? "" : " (new)"));
|
||||
paletteCounterLabel.setText(Container.i18n().format(I18n.ASSETS_COLOR_SCHEMA_DIALOG_COLOR_COUNT, orderedColors.size()));
|
||||
warningLabel.setText(orderedColors.size() > 15
|
||||
warningLabel.setText(orderedColors.size() > 16
|
||||
? Container.i18n().text(I18n.ASSETS_COLOR_SCHEMA_DIALOG_TOO_MANY_COLORS)
|
||||
: "");
|
||||
paletteStrip.getChildren().setAll(orderedColors.stream().map(this::swatch).toList());
|
||||
|
||||
@ -31,23 +31,26 @@ public record AssetStudioGlyphMaterialPalette(
|
||||
colors = List.copyOf(Objects.requireNonNull(colors, "colors"));
|
||||
}
|
||||
|
||||
public List<Integer> convertedRgb565Colors() {
|
||||
public List<Integer> convertedRgba8888Colors() {
|
||||
final List<Integer> converted = new ArrayList<>(colors.size());
|
||||
for (String color : colors) {
|
||||
converted.add(convertHtmlRgbToRgb565(color));
|
||||
converted.add(convertHtmlRgbToRgba8888(color));
|
||||
}
|
||||
return List.copyOf(converted);
|
||||
}
|
||||
|
||||
static int convertHtmlRgbToRgb565(String htmlRgb) {
|
||||
static int convertHtmlRgbToRgba8888(String htmlRgb) {
|
||||
final String value = Objects.requireNonNull(htmlRgb, "htmlRgb").trim();
|
||||
if (!value.matches("^#[0-9A-Fa-f]{6}$")) {
|
||||
throw new IllegalArgumentException("htmlRgb must use #RRGGBB format");
|
||||
|
||||
if (!value.matches("^#[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$")) {
|
||||
throw new IllegalArgumentException("htmlRgb must use #RRGGBB or #RRGGBBAA format");
|
||||
}
|
||||
|
||||
final int rgb = Integer.parseInt(value.substring(1), 16);
|
||||
final int red = (rgb >> 16) & 0xFF;
|
||||
final int green = (rgb >> 8) & 0xFF;
|
||||
final int blue = rgb & 0xFF;
|
||||
return ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);
|
||||
if (value.length() == 9) {
|
||||
return rgb;
|
||||
}
|
||||
|
||||
return (rgb << 8) | 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
@ -242,7 +242,7 @@ public final class AssetStudioMetadataService {
|
||||
try {
|
||||
final int index = Integer.parseInt(entry.getKey());
|
||||
final String color = normalizeHtmlColor(entry.getValue().asText(null));
|
||||
if (index >= 1 && index <= 15 && color != null) {
|
||||
if (index >= 0 && index <= 15 && color != null) {
|
||||
materialIndices.put(index, color);
|
||||
}
|
||||
} catch (NumberFormatException ignored) {
|
||||
@ -302,7 +302,7 @@ public final class AssetStudioMetadataService {
|
||||
if (indices.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
int expected = 1;
|
||||
int expected = 0;
|
||||
for (Integer index : new java.util.TreeSet<>(indices)) {
|
||||
if (index == null || index != expected) {
|
||||
return false;
|
||||
@ -317,7 +317,7 @@ public final class AssetStudioMetadataService {
|
||||
return null;
|
||||
}
|
||||
final String normalized = value.trim().toUpperCase();
|
||||
return normalized.matches("^#[0-9A-F]{6}$") ? normalized : null;
|
||||
return normalized.matches("^#[0-9A-F]{6}([0-9A-F]{2})?$") ? normalized : null;
|
||||
}
|
||||
|
||||
private AssetStudioSceneBankMetadata defaultSceneBankMetadata(Path supportFile) {
|
||||
|
||||
@ -250,7 +250,7 @@ assets.colorSchema.removeBlocked=Schema {0} cannot be removed while palettes sti
|
||||
assets.colorSchema.dialog.availableTiles=Available Tiles
|
||||
assets.colorSchema.dialog.assignedTiles=Assigned Tiles
|
||||
assets.colorSchema.dialog.colorCount=Color count: {0}
|
||||
assets.colorSchema.dialog.tooManyColors=Color Schema Groups support at most 15 visible indices.
|
||||
assets.colorSchema.dialog.tooManyColors=Color Schema Groups support at most 16 visible indices.
|
||||
assets.details.codecConfiguration.empty=This codec does not expose configuration fields yet.
|
||||
assets.details.metadata.empty=This asset does not expose metadata fields yet.
|
||||
assets.addWizard.title=Add Asset
|
||||
|
||||
@ -37,7 +37,7 @@ final class AssetDetailsPaletteOverhaulingCoordinatorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void initialSelectionStillMatchesBuildFacingPipelinePalettePayload() {
|
||||
void initialSelectionMatchesPersistedPackerPipelinePalettePayload() {
|
||||
final AssetDetailsPaletteOverhaulingCoordinator coordinator = new AssetDetailsPaletteOverhaulingCoordinator();
|
||||
final AssetStudioGlyphMaterialPalette persisted = new AssetStudioGlyphMaterialPalette(
|
||||
"palette-1",
|
||||
@ -56,8 +56,8 @@ final class AssetDetailsPaletteOverhaulingCoordinatorTest {
|
||||
List.of(persisted),
|
||||
null),
|
||||
List.of(Map.of(
|
||||
"originalArgb8888", List.of(0xFFFF0000, 0xFF00FF00),
|
||||
"convertedRgb565", List.of(0xF800, 0x07E0)))));
|
||||
"index", 0,
|
||||
"rgba8888", List.of(0xFF0000FF, 0x00FF00FF)))));
|
||||
|
||||
assertEquals(1, coordinator.viewModel().selectedFiles().size());
|
||||
assertEquals("palette-1", coordinator.viewModel().selectedFiles().getFirst().metadata().get("paletteId"));
|
||||
@ -283,7 +283,7 @@ final class AssetDetailsPaletteOverhaulingCoordinatorTest {
|
||||
null),
|
||||
List.of(Map.of(
|
||||
"originalArgb8888", List.of(0xFF112233, 0xFF445566),
|
||||
"convertedRgb565", List.of(0x1106, 0x42AC)))));
|
||||
"rgba8888", List.of(0x112233FF, 0x445566FF)))));
|
||||
|
||||
final AssetStudioGlyphMaterialMetadata metadata = coordinator.removePalette("palette-1");
|
||||
|
||||
@ -440,6 +440,6 @@ final class AssetDetailsPaletteOverhaulingCoordinatorTest {
|
||||
"paletteIndices", List.of(0, 1, 2, 0)),
|
||||
"palette", Map.of(
|
||||
"originalArgb8888", List.of(0xFFFF0000, 0xFF00FF00),
|
||||
"convertedRgb565", List.of(0xF800, 0x07E0))));
|
||||
"rgba8888", List.of(0xFF0000FF, 0x00FF00FF))));
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,6 +47,22 @@ final class AssetDetailsColorSchemaCoordinatorTest {
|
||||
assertEquals(List.of(), AssetDetailsColorSchemaCoordinator.tileColors(tile));
|
||||
}
|
||||
|
||||
@Test
|
||||
void tileColorsPreserveRgbaAlphaWhenAvailable() {
|
||||
final AssetWorkspaceBankCompositionFile tile = new AssetWorkspaceBankCompositionFile(
|
||||
"a.png",
|
||||
"a.png",
|
||||
64L,
|
||||
1L,
|
||||
"fp-a",
|
||||
Map.of(
|
||||
"artifactId", "artifact-a",
|
||||
"tile", Map.of("width", 8, "height", 8, "paletteIndices", List.of(0, 1)),
|
||||
"palette", Map.of("rgba8888", List.of(0x00000000, 0xFF0000FF))));
|
||||
|
||||
assertEquals(List.of("#00000000", "#FF0000"), AssetDetailsColorSchemaCoordinator.tileColors(tile));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createFlowUsesSelectedFingerprintWhenCurrentFileFingerprintIsMissing() {
|
||||
final AssetDetailsColorSchemaCoordinator coordinator = new AssetDetailsColorSchemaCoordinator();
|
||||
@ -95,7 +111,7 @@ final class AssetDetailsColorSchemaCoordinatorTest {
|
||||
|
||||
final AssetStudioGlyphMaterialSchema schema = updated.schemas().getFirst();
|
||||
assertEquals(2, schema.version());
|
||||
assertEquals(Map.of(1, "#FF0000", 2, "#00FF00"), schema.materialIndices());
|
||||
assertEquals(Map.of(0, "#FF0000", 1, "#00FF00"), schema.materialIndices());
|
||||
assertEquals(List.of("artifact-a", "artifact-b"), schema.artifactBindings().stream().map(AssetStudioGlyphMaterialArtifactBinding::artifactId).toList());
|
||||
}
|
||||
|
||||
@ -204,7 +220,7 @@ final class AssetDetailsColorSchemaCoordinatorTest {
|
||||
Map.of(
|
||||
"artifactId", artifactId,
|
||||
"tile", Map.of("width", 8, "height", 8, "paletteIndices", List.of(1, 1, 1)),
|
||||
"palette", Map.of("originalArgb8888", argb, "convertedRgb565", List.of())));
|
||||
"palette", Map.of("originalArgb8888", argb, "rgba8888", List.of())));
|
||||
}
|
||||
|
||||
private AssetWorkspaceBankCompositionFile selectedArtifactOnly(String path, String artifactId, String fingerprint) {
|
||||
|
||||
@ -51,7 +51,7 @@ final class AssetStudioMetadataServiceTest {
|
||||
"schema-1",
|
||||
AssetStudioGlyphMaterialSchemaStatus.OK,
|
||||
2,
|
||||
Map.of(1, "#8B4513", 2, "#00AA00"),
|
||||
Map.of(0, "#8B4513", 1, "#00AA0000"),
|
||||
List.of(
|
||||
new AssetStudioGlyphMaterialArtifactBinding("artifact-1", "fp-1"),
|
||||
new AssetStudioGlyphMaterialArtifactBinding("artifact-2", "fp-2")))),
|
||||
@ -60,7 +60,7 @@ final class AssetStudioMetadataServiceTest {
|
||||
"Forest",
|
||||
"schema-1",
|
||||
2,
|
||||
List.of("#8B4513", "#00AA00"))),
|
||||
List.of("#8B4513", "#00AA0000"))),
|
||||
null);
|
||||
service.writeGlyphMaterialMetadata(assetRoot, metadata);
|
||||
|
||||
@ -69,22 +69,22 @@ final class AssetStudioMetadataServiceTest {
|
||||
assertNotNull(snapshot.glyphMaterialMetadata());
|
||||
assertEquals(1, snapshot.glyphMaterialMetadata().schemas().size());
|
||||
assertEquals("schema-1", snapshot.glyphMaterialMetadata().schemas().getFirst().schemaId());
|
||||
assertEquals("#8B4513", snapshot.glyphMaterialMetadata().schemas().getFirst().materialIndices().get(1));
|
||||
assertEquals("#8B4513", snapshot.glyphMaterialMetadata().schemas().getFirst().materialIndices().get(0));
|
||||
assertEquals(1, snapshot.glyphMaterialMetadata().palettes().size());
|
||||
assertEquals("Forest", snapshot.glyphMaterialMetadata().palettes().getFirst().displayName());
|
||||
assertEquals(List.of("#8B4513", "#00AA00"), snapshot.glyphMaterialMetadata().palettes().getFirst().colors());
|
||||
assertEquals(List.of("#8B4513", "#00AA0000"), snapshot.glyphMaterialMetadata().palettes().getFirst().colors());
|
||||
}
|
||||
|
||||
@Test
|
||||
void keepsHtmlRgbAsCanonicalGlyphMaterialColorAndDerivesRgb565() {
|
||||
void keepsHtmlRgbAsCanonicalGlyphMaterialColorAndDerivesRgba8888() {
|
||||
final AssetStudioGlyphMaterialPalette palette = new AssetStudioGlyphMaterialPalette(
|
||||
"palette-1",
|
||||
"schema-1",
|
||||
1,
|
||||
List.of("#FF0000", "#00FF00", "#0000FF"));
|
||||
List.of("#FF0000", "#00FF00", "#0000FF", "#00000000"));
|
||||
|
||||
assertEquals(List.of("#FF0000", "#00FF00", "#0000FF"), palette.colors());
|
||||
assertEquals(List.of(0xF800, 0x07E0, 0x001F), palette.convertedRgb565Colors());
|
||||
assertEquals(List.of("#FF0000", "#00FF00", "#0000FF", "#00000000"), palette.colors());
|
||||
assertEquals(List.of(0xFF0000FF, 0x00FF00FF, 0x0000FFFF, 0x00000000), palette.convertedRgba8888Colors());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -6,36 +6,11 @@
|
||||
"asset_uuid" : "21953cb8-4101-4790-9e5e-d95f5fbc9b5a",
|
||||
"root" : "ui/atlas2",
|
||||
"included_in_build" : true
|
||||
}, {
|
||||
"asset_id" : 7,
|
||||
"asset_uuid" : "62a81570-8f47-4612-9288-6060e6c9a2e2",
|
||||
"root" : "ui/one-more-atlas",
|
||||
"included_in_build" : false
|
||||
}, {
|
||||
"asset_id" : 8,
|
||||
"asset_uuid" : "9a7386e7-6f0e-4e4c-9919-0de71e0b7031",
|
||||
"root" : "ui/sound",
|
||||
"included_in_build" : true
|
||||
}, {
|
||||
"asset_id" : 11,
|
||||
"asset_uuid" : "64147d33-e8bf-4272-bb5c-b4c07c0276b3",
|
||||
"root" : "bigode",
|
||||
"included_in_build" : false
|
||||
}, {
|
||||
"asset_id" : 12,
|
||||
"asset_uuid" : "b15b319f-5cab-4254-93ea-d83f4742d204",
|
||||
"root" : "recovered/atlas2",
|
||||
"included_in_build" : false
|
||||
}, {
|
||||
"asset_id" : 13,
|
||||
"asset_uuid" : "4d9847b0-5a23-421f-8b78-bf3909ca2281",
|
||||
"root" : "recovered/one-more-atlas",
|
||||
"included_in_build" : false
|
||||
}, {
|
||||
"asset_id" : 14,
|
||||
"asset_uuid" : "f64d3bfe-443d-4703-b62a-face19a32cac",
|
||||
"root" : "recovered/bbb2",
|
||||
"included_in_build" : false
|
||||
}, {
|
||||
"asset_id" : 15,
|
||||
"asset_uuid" : "87396aab-337e-479e-b1f4-ec296678389e",
|
||||
|
||||
@ -9,8 +9,7 @@
|
||||
"palettes" : [ {
|
||||
"index" : 0,
|
||||
"palette" : {
|
||||
"originalArgb8888" : [ -14671840, -11431408, -9326048, -802016, -3964416, -5086944, -789517, -810399, -1863087, -10403552, -5078447 ],
|
||||
"convertedRgb565" : [ 8452, 21634, 30084, 62980, 50176, 45828, 63390, 62732, 58506, 25092, 46090 ]
|
||||
"rgba8888" : [ 0, 538976511, 1368527103, 1907499263, -205315841, -1014890241, -1302257409, -202116097, -207461889, -476950017, 1631658239, -1300082177 ]
|
||||
}
|
||||
} ]
|
||||
},
|
||||
@ -27,41 +26,41 @@
|
||||
"file" : "link00.png",
|
||||
"index" : 0,
|
||||
"artifact_id" : "art-b82d5371eb9c",
|
||||
"fingerprint" : "c363f3ef7f32d9b249c7dc8babdb3eb5aae68c524099e5e97290202b054dda71"
|
||||
"fingerprint" : "c8e30f40473deedb265be26967f6bf5f2ba35a2bd5d0a049dcb8843fa60d5600"
|
||||
}, {
|
||||
"file" : "link01.png",
|
||||
"index" : 1,
|
||||
"artifact_id" : "art-acf4398779de",
|
||||
"fingerprint" : "e2130efbb9643b41a4fb62619f2713402f9622c7db040fe18d929609ef70b89a"
|
||||
"fingerprint" : "58a6a9bc02d9174fdc6bcd6f71f5fb86c47d506812e83cae5b0e23a1f951dbbf"
|
||||
}, {
|
||||
"file" : "link02.png",
|
||||
"index" : 2,
|
||||
"artifact_id" : "art-88d5de66ceea",
|
||||
"fingerprint" : "9216dfc6a6226fac398e9f781550d23fbfaa65bd377cdda69187149beca114ec"
|
||||
"fingerprint" : "110c4f835a40bb370c7bb7761afcd69de0d38e75798d20d50f4c203aa56f213a"
|
||||
}, {
|
||||
"file" : "link03.png",
|
||||
"index" : 3,
|
||||
"artifact_id" : "art-18a3b61a9561",
|
||||
"fingerprint" : "ebc4b7ca7bb1455288681c5d71424f60f658a44cb343d0c3934329cec676c867"
|
||||
"fingerprint" : "2e6fb2cfbf77f4b3c0d116b9b77a3ae228cdc5671bd18320285824faef9674d3"
|
||||
}, {
|
||||
"file" : "link04.png",
|
||||
"index" : 4,
|
||||
"artifact_id" : "art-231ecb85b204",
|
||||
"fingerprint" : "c363f3ef7f32d9b249c7dc8babdb3eb5aae68c524099e5e97290202b054dda71"
|
||||
"fingerprint" : "c8e30f40473deedb265be26967f6bf5f2ba35a2bd5d0a049dcb8843fa60d5600"
|
||||
}, {
|
||||
"file" : "link05.png",
|
||||
"index" : 5,
|
||||
"artifact_id" : "art-f36b8455db22",
|
||||
"fingerprint" : "1ba8ce75c445396334737143e0cfa56c87a1fb64cca8ad0962192594849249eb"
|
||||
"fingerprint" : "94ac699b5e913925cf881e76ff951875147b598b1ee9870187111e7ca0b40d1d"
|
||||
}, {
|
||||
"file" : "link06.png",
|
||||
"index" : 6,
|
||||
"artifact_id" : "art-884d6b34d741",
|
||||
"fingerprint" : "5e0954447699c6d5eac7550f1a10cf520efcddad0b9fbd9a716434da9d9550d4"
|
||||
"fingerprint" : "fc0a623650619ca39c1ce2f9f9e1b0f6951abd194e18ea60c682fae6a769df97"
|
||||
}, {
|
||||
"file" : "link07.png",
|
||||
"index" : 7,
|
||||
"artifact_id" : "art-ca5e39fb056c",
|
||||
"fingerprint" : "6104ba7f216a937994d478ed07766ec28f48ad72fdd6620508535a1f877b8ddb"
|
||||
"fingerprint" : "7ada8efefcd6d069b10846fefb9bd9cff1a1fa4c1565bbebfd68fc83645f0732"
|
||||
} ]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 507 B After Width: | Height: | Size: 476 B |
|
Before Width: | Height: | Size: 766 B After Width: | Height: | Size: 701 B |
|
Before Width: | Height: | Size: 684 B After Width: | Height: | Size: 626 B |
|
Before Width: | Height: | Size: 669 B After Width: | Height: | Size: 628 B |
@ -3,8 +3,9 @@
|
||||
"schemas" : [ {
|
||||
"schema_id" : "schema-1",
|
||||
"status" : "ok",
|
||||
"version" : 1,
|
||||
"version" : 2,
|
||||
"material_indices" : {
|
||||
"0" : "#00000000",
|
||||
"1" : "#202020",
|
||||
"2" : "#519210",
|
||||
"3" : "#71B220",
|
||||
@ -19,28 +20,28 @@
|
||||
},
|
||||
"artifact_bindings" : [ {
|
||||
"artifact_id" : "art-b82d5371eb9c",
|
||||
"fingerprint" : "c363f3ef7f32d9b249c7dc8babdb3eb5aae68c524099e5e97290202b054dda71"
|
||||
"fingerprint" : "c8e30f40473deedb265be26967f6bf5f2ba35a2bd5d0a049dcb8843fa60d5600"
|
||||
}, {
|
||||
"artifact_id" : "art-acf4398779de",
|
||||
"fingerprint" : "e2130efbb9643b41a4fb62619f2713402f9622c7db040fe18d929609ef70b89a"
|
||||
"fingerprint" : "58a6a9bc02d9174fdc6bcd6f71f5fb86c47d506812e83cae5b0e23a1f951dbbf"
|
||||
}, {
|
||||
"artifact_id" : "art-88d5de66ceea",
|
||||
"fingerprint" : "9216dfc6a6226fac398e9f781550d23fbfaa65bd377cdda69187149beca114ec"
|
||||
"fingerprint" : "110c4f835a40bb370c7bb7761afcd69de0d38e75798d20d50f4c203aa56f213a"
|
||||
}, {
|
||||
"artifact_id" : "art-18a3b61a9561",
|
||||
"fingerprint" : "ebc4b7ca7bb1455288681c5d71424f60f658a44cb343d0c3934329cec676c867"
|
||||
"fingerprint" : "2e6fb2cfbf77f4b3c0d116b9b77a3ae228cdc5671bd18320285824faef9674d3"
|
||||
}, {
|
||||
"artifact_id" : "art-231ecb85b204",
|
||||
"fingerprint" : "c363f3ef7f32d9b249c7dc8babdb3eb5aae68c524099e5e97290202b054dda71"
|
||||
"fingerprint" : "c8e30f40473deedb265be26967f6bf5f2ba35a2bd5d0a049dcb8843fa60d5600"
|
||||
}, {
|
||||
"artifact_id" : "art-f36b8455db22",
|
||||
"fingerprint" : "1ba8ce75c445396334737143e0cfa56c87a1fb64cca8ad0962192594849249eb"
|
||||
"fingerprint" : "94ac699b5e913925cf881e76ff951875147b598b1ee9870187111e7ca0b40d1d"
|
||||
}, {
|
||||
"artifact_id" : "art-884d6b34d741",
|
||||
"fingerprint" : "5e0954447699c6d5eac7550f1a10cf520efcddad0b9fbd9a716434da9d9550d4"
|
||||
"fingerprint" : "fc0a623650619ca39c1ce2f9f9e1b0f6951abd194e18ea60c682fae6a769df97"
|
||||
}, {
|
||||
"artifact_id" : "art-ca5e39fb056c",
|
||||
"fingerprint" : "6104ba7f216a937994d478ed07766ec28f48ad72fdd6620508535a1f877b8ddb"
|
||||
"fingerprint" : "7ada8efefcd6d069b10846fefb9bd9cff1a1fa4c1565bbebfd68fc83645f0732"
|
||||
} ]
|
||||
} ],
|
||||
"palettes" : [ ]
|
||||
|
||||
|
Before Width: | Height: | Size: 497 B After Width: | Height: | Size: 485 B |
|
Before Width: | Height: | Size: 500 B After Width: | Height: | Size: 496 B |
|
Before Width: | Height: | Size: 517 B After Width: | Height: | Size: 492 B |
|
Before Width: | Height: | Size: 507 B After Width: | Height: | Size: 500 B |
|
Before Width: | Height: | Size: 497 B After Width: | Height: | Size: 485 B |
|
Before Width: | Height: | Size: 495 B After Width: | Height: | Size: 487 B |
|
Before Width: | Height: | Size: 518 B After Width: | Height: | Size: 498 B |
|
Before Width: | Height: | Size: 517 B After Width: | Height: | Size: 509 B |
@ -1,17 +0,0 @@
|
||||
{
|
||||
"schema_version" : 1,
|
||||
"asset_uuid" : "64147d33-e8bf-4272-bb5c-b4c07c0276b3",
|
||||
"name" : "Bigode",
|
||||
"type" : "glyph_bank",
|
||||
"output" : {
|
||||
"format" : "GLYPH/indexed_v1",
|
||||
"codec" : "NONE",
|
||||
"codec_configuration" : { },
|
||||
"metadata" : {
|
||||
"tile_size" : "16x16"
|
||||
}
|
||||
},
|
||||
"preload" : {
|
||||
"enabled" : false
|
||||
}
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
{
|
||||
"schema_version" : 1,
|
||||
"glyph_bank_specialization" : "sprites"
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
{
|
||||
"schema_version" : 1,
|
||||
"asset_uuid" : "b15b319f-5cab-4254-93ea-d83f4742d204",
|
||||
"name" : "ui_atlas",
|
||||
"type" : "glyph_bank",
|
||||
"output" : {
|
||||
"format" : "GLYPH/indexed_v1",
|
||||
"codec" : "NONE",
|
||||
"codec_configuration" : { },
|
||||
"metadata" : {
|
||||
"tile_size" : "16x16"
|
||||
}
|
||||
},
|
||||
"preload" : {
|
||||
"enabled" : false
|
||||
},
|
||||
"artifacts" : [ {
|
||||
"file" : "confirm.png",
|
||||
"index" : 0,
|
||||
"artifact_id" : "art-8c6bdad24eb1",
|
||||
"fingerprint" : "aa7d241deabcebe29a6096e14eaf16fdc06cf06380c11a507620b00fc7bff094"
|
||||
} ]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 137 B |
@ -1,4 +0,0 @@
|
||||
{
|
||||
"schema_version" : 1,
|
||||
"glyph_bank_specialization" : "sprites"
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
{
|
||||
"schema_version" : 1,
|
||||
"asset_uuid" : "f64d3bfe-443d-4703-b62a-face19a32cac",
|
||||
"name" : "bbb2",
|
||||
"type" : "glyph_bank",
|
||||
"output" : {
|
||||
"codec" : "NONE",
|
||||
"format" : "GLYPH/indexed_v1"
|
||||
},
|
||||
"preload" : {
|
||||
"enabled" : false
|
||||
}
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
{
|
||||
"schema_version" : 1,
|
||||
"glyph_bank_specialization" : "sprites"
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
{
|
||||
"schema_version" : 1,
|
||||
"asset_uuid" : "4d9847b0-5a23-421f-8b78-bf3909ca2281",
|
||||
"name" : "one-more-atlas",
|
||||
"type" : "glyph_bank",
|
||||
"output" : {
|
||||
"format" : "GLYPH/indexed_v1",
|
||||
"codec" : "NONE"
|
||||
},
|
||||
"preload" : {
|
||||
"enabled" : true
|
||||
}
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
{
|
||||
"schema_version" : 1,
|
||||
"glyph_bank_specialization" : "sprites"
|
||||
}
|
||||
@ -9,8 +9,7 @@
|
||||
"palettes" : [ {
|
||||
"index" : 0,
|
||||
"palette" : {
|
||||
"originalArgb8888" : [ -12019640, -12552128, -14129088, -14133200, -14145496, -7835584, -5730208, -10465232 ],
|
||||
"convertedRgb565" : [ 19657, 17352, 11080, 10950, 10565, 35720, 44172, 25222 ]
|
||||
"rgba8888" : [ 1217939711, 1081622783, 677921023, 676868351, 673720575, -2005909249, -1466932993, 1615868159 ]
|
||||
}
|
||||
} ]
|
||||
},
|
||||
|
||||
@ -5,14 +5,14 @@
|
||||
"status" : "ok",
|
||||
"version" : 5,
|
||||
"material_indices" : {
|
||||
"1" : "#489848",
|
||||
"2" : "#407840",
|
||||
"3" : "#286840",
|
||||
"4" : "#285830",
|
||||
"5" : "#282828",
|
||||
"6" : "#887040",
|
||||
"7" : "#A89060",
|
||||
"8" : "#605030"
|
||||
"0" : "#489848",
|
||||
"1" : "#407840",
|
||||
"2" : "#286840",
|
||||
"3" : "#285830",
|
||||
"4" : "#282828",
|
||||
"5" : "#887040",
|
||||
"6" : "#A89060",
|
||||
"7" : "#605030"
|
||||
},
|
||||
"artifact_bindings" : [ {
|
||||
"artifact_id" : "art-67db3cae2605",
|
||||
|
||||
@ -1,32 +0,0 @@
|
||||
{
|
||||
"schema_version" : 1,
|
||||
"asset_uuid" : "21953cb8-4101-4790-9e5e-d95f5fbc9b5a",
|
||||
"name" : "ui_atlas",
|
||||
"type" : "glyph_bank",
|
||||
"output" : {
|
||||
"format" : "GLYPH/indexed_v1",
|
||||
"codec" : "NONE",
|
||||
"codec_configuration" : { },
|
||||
"metadata" : {
|
||||
"tile_size" : "32x32"
|
||||
},
|
||||
"pipeline" : {
|
||||
"palettes" : [ {
|
||||
"index" : 0,
|
||||
"palette" : {
|
||||
"originalArgb8888" : [ -265674, -1736296, -13905598, -11518505, -14439577, -2467509 ],
|
||||
"convertedRgb565" : [ 65414, 58387, 11912, 20986, 9548, 56009 ]
|
||||
}
|
||||
} ]
|
||||
}
|
||||
},
|
||||
"preload" : {
|
||||
"enabled" : false
|
||||
},
|
||||
"artifacts" : [ {
|
||||
"file" : "right-palette.png",
|
||||
"index" : 0,
|
||||
"artifact_id" : "art-92ac40fe2160",
|
||||
"fingerprint" : "0526996900bdeef23c72fe987fb45800f11895946dcd5f0f7f8390c879069a37"
|
||||
} ]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 137 B |
@ -1,21 +0,0 @@
|
||||
{
|
||||
"schema_version" : 1,
|
||||
"schemas" : [ {
|
||||
"schema_id" : "schema-1",
|
||||
"status" : "ok",
|
||||
"version" : 1,
|
||||
"material_indices" : {
|
||||
"1" : "#FBF236",
|
||||
"2" : "#E58198",
|
||||
"3" : "#2BD142",
|
||||
"4" : "#503DD7",
|
||||
"5" : "#23AB67",
|
||||
"6" : "#DA594B"
|
||||
},
|
||||
"artifact_bindings" : [ {
|
||||
"artifact_id" : "art-92ac40fe2160",
|
||||
"fingerprint" : "0526996900bdeef23c72fe987fb45800f11895946dcd5f0f7f8390c879069a37"
|
||||
} ]
|
||||
} ],
|
||||
"palettes" : [ ]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 172 B |
|
Before Width: | Height: | Size: 172 B |
|
Before Width: | Height: | Size: 172 B |
|
Before Width: | Height: | Size: 172 B |
|
Before Width: | Height: | Size: 172 B |
|
Before Width: | Height: | Size: 172 B |
|
Before Width: | Height: | Size: 172 B |
|
Before Width: | Height: | Size: 172 B |
|
Before Width: | Height: | Size: 172 B |
|
Before Width: | Height: | Size: 172 B |
|
Before Width: | Height: | Size: 172 B |
|
Before Width: | Height: | Size: 172 B |
|
Before Width: | Height: | Size: 172 B |