From 9c657b0450dd09aba1e62fa475f866fc98f7a504 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Thu, 19 Mar 2026 15:46:47 +0000 Subject: [PATCH] asset details (WIP) --- ...nd Virtual Asset Contract Specification.md | 3 + .../java/p/packer/PackerWorkspaceService.java | 2 + .../p/packer/dtos/PackerAssetDetailsDTO.java | 2 + .../ApplyPaletteOverhaulingRequest.java | 20 + .../ApplyPaletteOverhaulingResponse.java | 6 + .../packer/models/PackerAssetDeclaration.java | 15 + .../p/packer/models/PackerAssetDetails.java | 2 + .../FileSystemPackerWorkspaceService.java | 95 ++++- .../PackerAssetDeclarationParser.java | 47 ++ .../services/PackerAssetDetailsService.java | 48 +++ .../services/PackerReadMessageMapper.java | 1 + .../PackerRuntimeAssetMaterializerTest.java | 2 + .../FileSystemPackerWorkspaceServiceTest.java | 67 +++ .../PackerAssetDeclarationParserTest.java | 57 +++ .../PackerAssetDetailsServiceTest.java | 25 ++ .../controls/banks/StudioDualListView.java | 4 +- .../assets/details/AssetDetailsControl.java | 3 +- ...AssetDetailsPaletteOverhaulingControl.java | 65 ++- ...tDetailsPaletteOverhaulingCoordinator.java | 31 +- .../messages/AssetWorkspaceAssetDetails.java | 2 + ...DetailsBankCompositionCoordinatorTest.java | 2 + ...ailsPaletteOverhaulingCoordinatorTest.java | 1 + test-projects/main/.studio/activities.json | 400 +++++++++--------- .../main/assets/ui/atlas2/asset.json | 9 + 24 files changed, 694 insertions(+), 215 deletions(-) create mode 100644 prometeu-packer/prometeu-packer-api/src/main/java/p/packer/messages/ApplyPaletteOverhaulingRequest.java create mode 100644 prometeu-packer/prometeu-packer-api/src/main/java/p/packer/messages/ApplyPaletteOverhaulingResponse.java diff --git a/docs/packer/specs/3. Asset Declaration and Virtual Asset Contract Specification.md b/docs/packer/specs/3. Asset Declaration and Virtual Asset Contract Specification.md index 888c469a..9ba5bb21 100644 --- a/docs/packer/specs/3. Asset Declaration and Virtual Asset Contract Specification.md +++ b/docs/packer/specs/3. Asset Declaration and Virtual Asset Contract Specification.md @@ -86,12 +86,14 @@ Required baseline fields: Optional baseline field: - `output.metadata` +- `output.pipeline` Rules: - `output.format` defines the semantic/runtime format contract; - `output.codec` defines storage/extraction behavior; - `output.metadata` carries runtime-relevant format-specific detail; +- `output.pipeline` carries pipeline-injected metadata kept separate at authoring time; - codec must remain explicit and must not be hidden inside format naming. ## Metadata Source Segmentation and Runtime Sink @@ -101,6 +103,7 @@ Rules: Rules: - declaration-time metadata may come from multiple sources under the asset contract (for example, format metadata, codec-related metadata, and build/pipeline-derived declarations); +- `output.pipeline` may carry nested pipeline-derived metadata objects; - this segmentation exists for authoring clarity and does not define multiple runtime sinks; - runtime consumers must read effective metadata from the runtime asset entry metadata sink (`AssetEntry.metadata`); - convergence/normalization behavior is normative in the build artifact specification. diff --git a/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/PackerWorkspaceService.java b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/PackerWorkspaceService.java index a89ae10b..ae264ed9 100644 --- a/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/PackerWorkspaceService.java +++ b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/PackerWorkspaceService.java @@ -24,4 +24,6 @@ public interface PackerWorkspaceService { UpdateAssetContractResponse updateAssetContract(UpdateAssetContractRequest request); ApplyBankCompositionResponse applyBankComposition(ApplyBankCompositionRequest request); + + ApplyPaletteOverhaulingResponse applyPaletteOverhauling(ApplyPaletteOverhaulingRequest request); } diff --git a/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/dtos/PackerAssetDetailsDTO.java b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/dtos/PackerAssetDetailsDTO.java index c5b54056..2074dfce 100644 --- a/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/dtos/PackerAssetDetailsDTO.java +++ b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/dtos/PackerAssetDetailsDTO.java @@ -15,6 +15,7 @@ public record PackerAssetDetailsDTO( Map> codecConfigurationFieldsByCodec, List metadataFields, PackerBankCompositionDetailsDTO bankComposition, + List> pipelinePalettes, List diagnostics) { public PackerAssetDetailsDTO { @@ -25,6 +26,7 @@ public record PackerAssetDetailsDTO( codecConfigurationFieldsByCodec = Map.copyOf(Objects.requireNonNull(codecConfigurationFieldsByCodec, "codecConfigurationFieldsByCodec")); metadataFields = List.copyOf(Objects.requireNonNull(metadataFields, "metadataFields")); bankComposition = Objects.requireNonNull(bankComposition, "bankComposition"); + pipelinePalettes = List.copyOf(Objects.requireNonNull(pipelinePalettes, "pipelinePalettes")); diagnostics = List.copyOf(Objects.requireNonNull(diagnostics, "diagnostics")); } } diff --git a/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/messages/ApplyPaletteOverhaulingRequest.java b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/messages/ApplyPaletteOverhaulingRequest.java new file mode 100644 index 00000000..455e4ff9 --- /dev/null +++ b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/messages/ApplyPaletteOverhaulingRequest.java @@ -0,0 +1,20 @@ +package p.packer.messages; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public record ApplyPaletteOverhaulingRequest( + PackerProjectContext project, + AssetReference assetReference, + List> selectedPalettes) { + + public ApplyPaletteOverhaulingRequest { + Objects.requireNonNull(project, "project"); + Objects.requireNonNull(assetReference, "assetReference"); + selectedPalettes = List.copyOf(Objects.requireNonNull(selectedPalettes, "selectedPalettes").stream() + .map(palette -> Map.copyOf(new LinkedHashMap<>(Objects.requireNonNull(palette, "palette")))) + .toList()); + } +} diff --git a/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/messages/ApplyPaletteOverhaulingResponse.java b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/messages/ApplyPaletteOverhaulingResponse.java new file mode 100644 index 00000000..66eb78c6 --- /dev/null +++ b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/messages/ApplyPaletteOverhaulingResponse.java @@ -0,0 +1,6 @@ +package p.packer.messages; + +public record ApplyPaletteOverhaulingResponse( + boolean success, + String errorMessage) { +} diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerAssetDeclaration.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerAssetDeclaration.java index 4a71685e..caca81f9 100644 --- a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerAssetDeclaration.java +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerAssetDeclaration.java @@ -1,10 +1,12 @@ package p.packer.models; +import com.fasterxml.jackson.databind.JsonNode; import org.apache.commons.lang3.StringUtils; import p.packer.messages.assets.AssetFamilyCatalog; import p.packer.messages.assets.OutputCodecCatalog; import p.packer.messages.assets.OutputFormatCatalog; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -18,6 +20,7 @@ public record PackerAssetDeclaration( OutputFormatCatalog outputFormat, OutputCodecCatalog outputCodec, Map outputMetadata, + Map outputPipelineMetadata, boolean preloadEnabled) { public PackerAssetDeclaration { @@ -31,8 +34,20 @@ public record PackerAssetDeclaration( outputFormat = Objects.requireNonNull(outputFormat, "outputFormat"); outputCodec = Objects.requireNonNull(outputCodec, "outputCodec"); outputMetadata = Map.copyOf(Objects.requireNonNull(outputMetadata, "outputMetadata")); + outputPipelineMetadata = immutablePipelineMetadata(outputPipelineMetadata); if (StringUtils.isBlank(assetUuid) || StringUtils.isBlank(name)) { throw new IllegalArgumentException("declaration fields must not be blank"); } } + + private static Map immutablePipelineMetadata(Map pipelineMetadata) { + final Map sanitized = new LinkedHashMap<>(); + Objects.requireNonNull(pipelineMetadata, "outputPipelineMetadata").forEach((key, value) -> { + if (key == null || key.isBlank() || value == null) { + return; + } + sanitized.put(key.trim(), value.deepCopy()); + }); + return Map.copyOf(sanitized); + } } diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerAssetDetails.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerAssetDetails.java index f1f1bacb..05f2fe7f 100644 --- a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerAssetDetails.java +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerAssetDetails.java @@ -15,6 +15,7 @@ public record PackerAssetDetails( Map> codecConfigurationFieldsByCodec, List metadataFields, PackerBankCompositionDetails bankComposition, + List> pipelinePalettes, List diagnostics) { public PackerAssetDetails { @@ -25,6 +26,7 @@ public record PackerAssetDetails( codecConfigurationFieldsByCodec = Map.copyOf(Objects.requireNonNull(codecConfigurationFieldsByCodec, "codecConfigurationFieldsByCodec")); metadataFields = List.copyOf(Objects.requireNonNull(metadataFields, "metadataFields")); bankComposition = Objects.requireNonNull(bankComposition, "bankComposition"); + pipelinePalettes = List.copyOf(Objects.requireNonNull(pipelinePalettes, "pipelinePalettes")); diagnostics = List.copyOf(Objects.requireNonNull(diagnostics, "diagnostics")); } } diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/FileSystemPackerWorkspaceService.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/FileSystemPackerWorkspaceService.java index b90eed0d..525ca12f 100644 --- a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/FileSystemPackerWorkspaceService.java +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/FileSystemPackerWorkspaceService.java @@ -2,6 +2,7 @@ package p.packer.services; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import p.packer.PackerWorkspacePaths; import p.packer.PackerWorkspaceService; @@ -541,7 +542,8 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe manifest.put("type", request.assetFamily().manifestType()); manifest.put("output", Map.of( "format", request.outputFormat().manifestValue(), - "codec", request.outputCodec().manifestValue())); + "codec", request.outputCodec().manifestValue(), + "pipeline", Map.of())); manifest.put("preload", Map.of("enabled", request.preloadEnabled())); mapper.writerWithDefaultPrettyPrinter().writeValue(manifestPath.toFile(), manifest); } @@ -665,6 +667,13 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe return writeCoordinator.execute(project, () -> applyBankCompositionInWriteLane(safeRequest)); } + @Override + public ApplyPaletteOverhaulingResponse applyPaletteOverhauling(final ApplyPaletteOverhaulingRequest request) { + final ApplyPaletteOverhaulingRequest safeRequest = Objects.requireNonNull(request, "request"); + final PackerProjectContext project = safeRequest.project(); + return writeCoordinator.execute(project, () -> applyPaletteOverhaulingInWriteLane(safeRequest)); + } + private UpdateAssetContractResponse updateAssetContractInWriteLane(UpdateAssetContractRequest request) { final PackerProjectContext project = request.project(); workspaceFoundation.initWorkspace(new InitWorkspaceRequest(project)); @@ -772,6 +781,55 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe } } + private ApplyPaletteOverhaulingResponse applyPaletteOverhaulingInWriteLane(ApplyPaletteOverhaulingRequest request) { + final PackerProjectContext project = request.project(); + workspaceFoundation.initWorkspace(new InitWorkspaceRequest(project)); + final PackerRuntimeSnapshot snapshot = runtimeRegistry.getOrLoad(project).snapshot(); + final PackerDeleteAssetEvaluation evaluation = actionReadService.evaluateDelete(snapshot, project, request.assetReference()); + if (!evaluation.canDelete()) { + return new ApplyPaletteOverhaulingResponse( + false, + Objects.requireNonNullElse(evaluation.reason(), "Asset palette overhauling cannot be updated.")); + } + if (request.selectedPalettes().isEmpty()) { + return new ApplyPaletteOverhaulingResponse(false, "At least one palette must be selected."); + } + + final Path assetRoot = evaluation.resolved().assetRoot(); + final Path manifestPath = assetRoot.resolve("asset.json"); + if (!Files.isRegularFile(manifestPath)) { + return new ApplyPaletteOverhaulingResponse(false, "asset.json was not found for the requested asset root."); + } + + final ObjectNode manifest; + try { + final JsonNode rawManifest = mapper.readTree(manifestPath.toFile()); + if (!(rawManifest instanceof ObjectNode objectNode)) { + return new ApplyPaletteOverhaulingResponse(false, "asset.json must contain a JSON object at the root."); + } + manifest = objectNode; + } catch (IOException exception) { + return new ApplyPaletteOverhaulingResponse(false, "Unable to read asset manifest: " + exception.getMessage()); + } + + try { + patchManifestPipelinePalettes(manifest, request); + mapper.writerWithDefaultPrettyPrinter().writeValue(manifestPath.toFile(), manifest); + final var runtime = runtimeRegistry.update(project, (currentSnapshot, generation) -> runtimePatchService.afterUpdateAssetContract( + currentSnapshot, + generation, + assetRoot, + manifestPath, + evaluation.resolved().registryEntry())); + saveRuntimeCache(project, runtime.snapshot()); + return new ApplyPaletteOverhaulingResponse(true, null); + } catch (IOException exception) { + return new ApplyPaletteOverhaulingResponse(false, "Unable to update asset palette overhauling: " + exception.getMessage()); + } catch (RuntimeException exception) { + return new ApplyPaletteOverhaulingResponse(false, "Unable to update runtime snapshot: " + exception.getMessage()); + } + } + private OutputFormatCatalog resolveManifestOutputFormat(ObjectNode manifest) { final JsonNode outputNode = manifest.get("output"); if (!(outputNode instanceof ObjectNode outputObject)) { @@ -832,6 +890,41 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe } } + private void patchManifestPipelinePalettes(ObjectNode manifest, ApplyPaletteOverhaulingRequest request) { + final ObjectNode outputNode = mutableObject(manifest, "output"); + final ObjectNode pipelineNode = mutableObject(outputNode, "pipeline"); + final ArrayNode palettesNode = pipelineNode.putArray("palettes"); + for (Map palette : request.selectedPalettes()) { + final List originalArgb8888 = integerList(palette.get("originalArgb8888")); + final List convertedRgb565 = integerList(palette.get("convertedRgb565")); + if (originalArgb8888.isEmpty() || convertedRgb565.isEmpty()) { + throw new IllegalArgumentException("Each selected palette must contain originalArgb8888 and convertedRgb565 entries."); + } + final ObjectNode paletteNode = palettesNode.addObject(); + final ArrayNode originalNode = paletteNode.putArray("originalArgb8888"); + originalArgb8888.forEach(originalNode::add); + final ArrayNode convertedNode = paletteNode.putArray("convertedRgb565"); + convertedRgb565.forEach(convertedNode::add); + } + if (palettesNode.isEmpty()) { + throw new IllegalArgumentException("At least one palette must be selected."); + } + } + + private List integerList(Object value) { + if (!(value instanceof Iterable iterable)) { + return List.of(); + } + final List numbers = new ArrayList<>(); + for (Object item : iterable) { + if (!(item instanceof Number number)) { + return List.of(); + } + numbers.add(number.intValue()); + } + return List.copyOf(numbers); + } + private String contractFingerprint(ObjectNode manifest) { final JsonNode outputNode = manifest.path("output"); if (!(outputNode instanceof ObjectNode outputObject)) { diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerAssetDeclarationParser.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerAssetDeclarationParser.java index 6de61f28..cb907b4d 100644 --- a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerAssetDeclarationParser.java +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerAssetDeclarationParser.java @@ -50,6 +50,7 @@ public final class PackerAssetDeclarationParser { final var outputFormat = requiredOutputFormat(root.path("output"), diagnostics, manifestPath); final var outputCodec = requiredOutputCodec(root.path("output"), diagnostics, manifestPath); final var outputMetadata = optionalOutputMetadata(root.path("output"), diagnostics, manifestPath); + final var outputPipelineMetadata = optionalOutputPipelineMetadata(root.path("output"), diagnostics, manifestPath); final var preloadEnabled = requiredBoolean(root.path("preload"), "enabled", diagnostics, manifestPath); if (schemaVersion != null && !SUPPORTED_SCHEMA_VERSIONS.contains(schemaVersion)) { @@ -75,6 +76,7 @@ public final class PackerAssetDeclarationParser { outputFormat, outputCodec, outputMetadata, + outputPipelineMetadata, preloadEnabled), diagnostics); } @@ -228,6 +230,51 @@ public final class PackerAssetDeclarationParser { return Map.copyOf(metadata); } + private Map optionalOutputPipelineMetadata( + final JsonNode node, + final List diagnostics, + final Path manifestPath) { + final JsonNode pipelineNode = node.path("pipeline"); + if (pipelineNode.isMissingNode() || pipelineNode.isNull()) { + return Map.of(); + } + if (!pipelineNode.isObject()) { + diagnostics.add(new PackerDiagnostic( + PackerDiagnosticSeverity.ERROR, + PackerDiagnosticCategory.STRUCTURAL, + "Field 'output.pipeline' must be a JSON object.", + manifestPath, + true)); + return Map.of(); + } + + final Map pipelineMetadata = new LinkedHashMap<>(); + pipelineNode.fields().forEachRemaining(entry -> { + final String key = Objects.requireNonNullElse(entry.getKey(), "").trim(); + if (key.isBlank()) { + diagnostics.add(new PackerDiagnostic( + PackerDiagnosticSeverity.ERROR, + PackerDiagnosticCategory.STRUCTURAL, + "Field 'output.pipeline' has an invalid empty key.", + manifestPath, + true)); + return; + } + final JsonNode valueNode = entry.getValue(); + if (valueNode == null) { + diagnostics.add(new PackerDiagnostic( + PackerDiagnosticSeverity.ERROR, + PackerDiagnosticCategory.STRUCTURAL, + "Field 'output.pipeline' values must be valid JSON values.", + manifestPath, + true)); + return; + } + pipelineMetadata.put(key, valueNode.deepCopy()); + }); + return Map.copyOf(pipelineMetadata); + } + private void rejectLegacyInputs( final JsonNode node, final List diagnostics, diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerAssetDetailsService.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerAssetDetailsService.java index 01d52e4b..ec3b64e9 100644 --- a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerAssetDetailsService.java +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerAssetDetailsService.java @@ -1,5 +1,8 @@ package p.packer.services; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import p.packer.PackerWorkspacePaths; import p.packer.messages.*; import p.packer.messages.assets.*; @@ -80,6 +83,7 @@ public final class PackerAssetDetailsService { outputContract.codecConfigurationFieldsByCodec(), metadataFields(outputContract.metadataFields(), declaration.outputMetadata()), resolveBankCompositionDetails(snapshot, runtimeAsset, declaration), + resolvePipelinePalettes(declaration), diagnostics); return new GetAssetDetailsResult( diagnostics.stream().anyMatch(PackerDiagnostic::blocking) ? PackerOperationStatus.PARTIAL : PackerOperationStatus.SUCCESS, @@ -119,6 +123,7 @@ public final class PackerAssetDetailsService { Map.of(OutputCodecCatalog.NONE, List.of()), List.of(), PackerBankCompositionDetails.EMPTY, + List.of(), diagnostics); return new GetAssetDetailsResult( PackerOperationStatus.FAILED, @@ -160,6 +165,49 @@ public final class PackerAssetDetailsService { .toList(); } + private List> resolvePipelinePalettes(PackerAssetDeclaration declaration) { + final JsonNode palettesNode = declaration.outputPipelineMetadata().get("palettes"); + if (!(palettesNode instanceof ArrayNode palettesArray)) { + return List.of(); + } + final List> palettes = new ArrayList<>(); + for (JsonNode paletteNode : palettesArray) { + final Map normalized = palettePayloadFromNode(paletteNode); + if (!normalized.isEmpty()) { + palettes.add(normalized); + } + } + return List.copyOf(palettes); + } + + private Map palettePayloadFromNode(JsonNode paletteNode) { + if (!(paletteNode instanceof ObjectNode paletteObject)) { + return Map.of(); + } + final JsonNode originalNode = paletteObject.path("originalArgb8888"); + final JsonNode convertedNode = paletteObject.path("convertedRgb565"); + if (!originalNode.isArray() || !convertedNode.isArray()) { + return Map.of(); + } + final List originalArgb8888 = new ArrayList<>(); + for (JsonNode colorNode : originalNode) { + if (!colorNode.canConvertToInt()) { + return Map.of(); + } + originalArgb8888.add(colorNode.intValue()); + } + final List convertedRgb565 = new ArrayList<>(); + for (JsonNode colorNode : convertedNode) { + if (!colorNode.canConvertToInt()) { + return Map.of(); + } + convertedRgb565.add(colorNode.intValue()); + } + return Map.of( + "originalArgb8888", List.copyOf(originalArgb8888), + "convertedRgb565", List.copyOf(convertedRgb565)); + } + private Optional resolveSelectedBankFile( Path assetRoot, String relativePath, diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerReadMessageMapper.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerReadMessageMapper.java index 773ac2ba..46d90073 100644 --- a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerReadMessageMapper.java +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerReadMessageMapper.java @@ -32,6 +32,7 @@ public final class PackerReadMessageMapper { toCodecConfigurationFieldsByCodecDTO(details.codecConfigurationFieldsByCodec()), toCodecConfigurationFieldDTOs(details.metadataFields()), toBankCompositionDetailsDTO(details.bankComposition()), + details.pipelinePalettes().stream().map(PackerReadMessageMapper::normalizeMetadata).toList(), toDiagnosticDTOs(details.diagnostics())); } diff --git a/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/repositories/PackerRuntimeAssetMaterializerTest.java b/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/repositories/PackerRuntimeAssetMaterializerTest.java index 31d3d910..d9d906e8 100644 --- a/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/repositories/PackerRuntimeAssetMaterializerTest.java +++ b/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/repositories/PackerRuntimeAssetMaterializerTest.java @@ -1,5 +1,6 @@ package p.packer.repositories; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -77,6 +78,7 @@ final class PackerRuntimeAssetMaterializerTest { OutputFormatCatalog.TILES_INDEXED_V1, OutputCodecCatalog.NONE, metadata, + Map.of(), true); } diff --git a/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/FileSystemPackerWorkspaceServiceTest.java b/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/FileSystemPackerWorkspaceServiceTest.java index c3370ae0..b7b5e4bf 100644 --- a/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/FileSystemPackerWorkspaceServiceTest.java +++ b/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/FileSystemPackerWorkspaceServiceTest.java @@ -1,6 +1,7 @@ package p.packer.services; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import p.packer.events.PackerEvent; @@ -106,6 +107,7 @@ final class FileSystemPackerWorkspaceServiceTest { assertTrue(Files.isRegularFile(projectRoot.resolve("assets/ui/new-atlas/asset.json"))); final String manifestJson = Files.readString(projectRoot.resolve("assets/ui/new-atlas/asset.json")); assertTrue(manifestJson.contains("\"asset_uuid\"")); + assertTrue(manifestJson.contains("\"pipeline\"")); final var snapshot = service.listAssets(new ListAssetsRequest(project(projectRoot))); assertEquals(1, snapshot.assets().size()); @@ -216,6 +218,33 @@ final class FileSystemPackerWorkspaceServiceTest { assertEquals("mono", manifest.path("output").path("codec_configuration").path("palette").asText()); assertEquals("16x16", manifest.path("output").path("metadata").path("tile_size").asText()); assertEquals("1", manifest.path("output").path("metadata").path("channels").asText()); + assertTrue(manifest.path("output").path("pipeline").isMissingNode() || manifest.path("output").path("pipeline").isObject()); + } + + @Test + void updateAssetContractPreservesPipelineMetadataUnderOutput() throws Exception { + final Path projectRoot = copyFixture("workspaces/managed-basic", tempDir.resolve("update-contract-pipeline")); + final Path manifestPath = projectRoot.resolve("assets/ui/atlas/asset.json"); + final ObjectNode manifest = (ObjectNode) MAPPER.readTree(manifestPath.toFile()); + final ObjectNode pipelineNode = ((ObjectNode) manifest.path("output")).putObject("pipeline"); + pipelineNode.putObject("samples").putObject("1").put("offset", 0).put("length", 64); + pipelineNode.put("normalized", true); + MAPPER.writerWithDefaultPrettyPrinter().writeValue(manifestPath.toFile(), manifest); + + final FileSystemPackerWorkspaceService service = service(); + final var result = service.updateAssetContract(new UpdateAssetContractRequest( + project(projectRoot), + AssetReference.forAssetId(1), + false, + OutputCodecCatalog.NONE, + Map.of("NONE:packMode", "tight"), + Map.of("tile_size", "16x16"))); + + assertTrue(result.success()); + + final var updatedManifest = MAPPER.readTree(manifestPath.toFile()); + assertTrue(updatedManifest.path("output").path("pipeline").path("normalized").asBoolean()); + assertEquals(64, updatedManifest.path("output").path("pipeline").path("samples").path("1").path("length").asInt()); } @Test @@ -345,6 +374,44 @@ final class FileSystemPackerWorkspaceServiceTest { assertEquals(1, loader.loadCount()); } + @Test + void applyPaletteOverhaulingWritesPipelinePalettesInOrder() throws Exception { + final Path projectRoot = copyFixture("workspaces/managed-basic", tempDir.resolve("apply-palette-overhauling")); + final FileSystemPackerWorkspaceService service = service(); + + final var response = service.applyPaletteOverhauling(new ApplyPaletteOverhaulingRequest( + project(projectRoot), + AssetReference.forAssetId(1), + List.of( + Map.of( + "originalArgb8888", List.of(0xFFFF0000, 0xFF00FF00), + "convertedRgb565", List.of(0xF800, 0x07E0)), + Map.of( + "originalArgb8888", List.of(0xFF0000FF), + "convertedRgb565", List.of(0x001F))))); + + 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(0xFFFF0000, manifest.path("output").path("pipeline").path("palettes").get(0).path("originalArgb8888").get(0).asInt()); + assertEquals(0x001F, manifest.path("output").path("pipeline").path("palettes").get(1).path("convertedRgb565").get(0).asInt()); + } + + @Test + void applyPaletteOverhaulingFailsWhenSelectionIsEmpty() throws Exception { + final Path projectRoot = copyFixture("workspaces/managed-basic", tempDir.resolve("apply-palette-overhauling-empty")); + final FileSystemPackerWorkspaceService service = service(); + + final var response = service.applyPaletteOverhauling(new ApplyPaletteOverhaulingRequest( + project(projectRoot), + AssetReference.forAssetId(1), + List.of())); + + assertFalse(response.success()); + assertTrue(response.errorMessage().contains("At least one palette")); + } + @Test void deepSyncPreservesContractDrivenBankInvalidationAfterPatchUpdate() throws Exception { final Path projectRoot = copyFixture("workspaces/managed-basic", tempDir.resolve("deep-sync-bank-composition")); diff --git a/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/PackerAssetDeclarationParserTest.java b/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/PackerAssetDeclarationParserTest.java index 70680f0b..0853c42d 100644 --- a/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/PackerAssetDeclarationParserTest.java +++ b/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/PackerAssetDeclarationParserTest.java @@ -10,6 +10,7 @@ import p.packer.testing.PackerFixtureLocator; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Map; import static org.junit.jupiter.api.Assertions.*; @@ -31,9 +32,65 @@ final class PackerAssetDeclarationParserTest { assertEquals(AssetFamilyCatalog.TILE_BANK, result.declaration().assetFamily()); assertEquals("TILES/indexed_v1", result.declaration().outputFormat().displayName()); assertEquals(OutputCodecCatalog.NONE, result.declaration().outputCodec()); + assertEquals(Map.of(), result.declaration().outputPipelineMetadata()); assertTrue(result.declaration().preloadEnabled()); } + @Test + void parsesPipelineMetadataAsDedicatedOutputObject() throws Exception { + final Path manifest = tempDir.resolve("asset.json"); + Files.writeString(manifest, """ + { + "schema_version": 1, + "asset_uuid": "uuid-pipeline", + "name": "pipeline_asset", + "type": "sound_bank", + "output": { + "format": "SOUND/v1", + "codec": "NONE", + "pipeline": { + "samples": { + "1": { "offset": 0, "length": 128 } + }, + "normalized": true + } + }, + "preload": { "enabled": true } + } + """); + + final var result = parser.parse(manifest); + + assertTrue(result.valid()); + assertEquals(2, result.declaration().outputPipelineMetadata().size()); + assertTrue(result.declaration().outputPipelineMetadata().get("normalized").asBoolean()); + assertEquals(128, result.declaration().outputPipelineMetadata().get("samples").path("1").path("length").asInt()); + } + + @Test + void rejectsNonObjectPipelineMetadata() throws Exception { + final Path manifest = tempDir.resolve("asset.json"); + Files.writeString(manifest, """ + { + "schema_version": 1, + "asset_uuid": "uuid-pipeline", + "name": "pipeline_asset", + "type": "tile_bank", + "output": { + "format": "TILES/indexed_v1", + "codec": "NONE", + "pipeline": [] + }, + "preload": { "enabled": true } + } + """); + + final var result = parser.parse(manifest); + + assertFalse(result.valid()); + assertTrue(result.diagnostics().stream().anyMatch(diagnostic -> diagnostic.message().contains("output.pipeline"))); + } + @Test void rejectsMalformedJsonWithStructuralDiagnostic() { final var result = parser.parse(PackerFixtureLocator.fixtureRoot("workspaces/invalid-malformed/assets/bad/asset.json")); diff --git a/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/PackerAssetDetailsServiceTest.java b/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/PackerAssetDetailsServiceTest.java index 452544a6..3beaa3de 100644 --- a/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/PackerAssetDetailsServiceTest.java +++ b/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/PackerAssetDetailsServiceTest.java @@ -49,10 +49,35 @@ final class PackerAssetDetailsServiceTest { assertEquals(List.of(), result.details().codecConfigurationFieldsByCodec().get(OutputCodecCatalog.NONE)); assertNotNull(result.details().bankComposition()); assertTrue(result.details().bankComposition().selectedFiles().isEmpty()); + assertTrue(result.details().pipelinePalettes().isEmpty()); assertTrue(result.diagnostics().stream().noneMatch(diagnostic -> diagnostic.blocking())); assertTrue(result.diagnostics().stream().anyMatch(diagnostic -> diagnostic.message().contains("Output metadata for tile bank cannot be empty"))); } + @Test + void exposesPipelinePalettesFromOutputPipelineMetadata() throws Exception { + final Path projectRoot = copyFixture("workspaces/managed-basic", tempDir.resolve("managed-pipeline-palettes")); + final Path manifestPath = projectRoot.resolve("assets/ui/atlas/asset.json"); + + final ObjectMapper mapper = new ObjectMapper(); + final ObjectNode manifest = (ObjectNode) mapper.readTree(manifestPath.toFile()); + final ObjectNode pipeline = ((ObjectNode) manifest.path("output")).putObject("pipeline"); + final var palettes = pipeline.putArray("palettes"); + palettes.addObject() + .putArray("originalArgb8888").add(0xFFFF0000).add(0xFF00FF00); + ((ObjectNode) palettes.get(0)) + .putArray("convertedRgb565").add(0xF800).add(0x07E0); + mapper.writerWithDefaultPrettyPrinter().writeValue(manifestPath.toFile(), manifest); + + final PackerAssetDetailsService service = service(); + final var result = service.getAssetDetails(new GetAssetDetailsRequest(project(projectRoot), AssetReference.forAssetId(1))); + + assertEquals(1, result.details().pipelinePalettes().size()); + assertEquals( + List.of(0xFFFF0000, 0xFF00FF00), + result.details().pipelinePalettes().getFirst().get("originalArgb8888")); + } + @Test void projectsBankCompositionAvailableAndSelectedFiles() throws Exception { final Path projectRoot = copyFixture("workspaces/managed-basic", tempDir.resolve("managed-bank-composition")); diff --git a/prometeu-studio/src/main/java/p/studio/controls/banks/StudioDualListView.java b/prometeu-studio/src/main/java/p/studio/controls/banks/StudioDualListView.java index 759b956b..6c0e35e7 100644 --- a/prometeu-studio/src/main/java/p/studio/controls/banks/StudioDualListView.java +++ b/prometeu-studio/src/main/java/p/studio/controls/banks/StudioDualListView.java @@ -203,7 +203,7 @@ public abstract class StudioDualListView extends HBox { return; } final String text = indexed - ? (getIndex() + 1) + ". " + itemText(item) + ? getIndex() + ". " + itemText(item) : itemText(item); setText(text); setGraphic(null); @@ -259,7 +259,7 @@ public abstract class StudioDualListView extends HBox { } private Node createIndexedGraphic(Node graphic, int index) { - final Label chip = new Label((index + 1) + "."); + final Label chip = new Label(index + "."); chip.getStyleClass().add("studio-dual-list-index-chip"); final HBox wrapper = new HBox(8, chip, graphic); wrapper.getStyleClass().add("studio-dual-list-indexed-cell"); diff --git a/prometeu-studio/src/main/java/p/studio/workspaces/assets/details/AssetDetailsControl.java b/prometeu-studio/src/main/java/p/studio/workspaces/assets/details/AssetDetailsControl.java index 37af5fdc..35e31463 100644 --- a/prometeu-studio/src/main/java/p/studio/workspaces/assets/details/AssetDetailsControl.java +++ b/prometeu-studio/src/main/java/p/studio/workspaces/assets/details/AssetDetailsControl.java @@ -73,7 +73,7 @@ public final class AssetDetailsControl extends VBox implements StudioEventAware this.summaryControl = new AssetDetailsSummaryControl(projectReference, workspaceBus); this.contractControl = new AssetDetailsContractControl(projectReference, workspaceBus); this.bankCompositionControl = new AssetDetailsBankCompositionControl(projectReference, workspaceBus); - this.paletteOverhaulingControl = new AssetDetailsPaletteOverhaulingControl(workspaceBus); + this.paletteOverhaulingControl = new AssetDetailsPaletteOverhaulingControl(projectReference, workspaceBus); this.actionsSection = createActionsSection(); getStyleClass().add("assets-workspace-pane"); @@ -556,6 +556,7 @@ public final class AssetDetailsControl extends VBox implements StudioEventAware details.codecConfigurationFieldsByCodec(), details.metadataFields(), mapBankComposition(details.bankComposition()), + details.pipelinePalettes(), mergedDiagnostics); } diff --git a/prometeu-studio/src/main/java/p/studio/workspaces/assets/details/palette/AssetDetailsPaletteOverhaulingControl.java b/prometeu-studio/src/main/java/p/studio/workspaces/assets/details/palette/AssetDetailsPaletteOverhaulingControl.java index add57080..fad8cc63 100644 --- a/prometeu-studio/src/main/java/p/studio/workspaces/assets/details/palette/AssetDetailsPaletteOverhaulingControl.java +++ b/prometeu-studio/src/main/java/p/studio/workspaces/assets/details/palette/AssetDetailsPaletteOverhaulingControl.java @@ -1,23 +1,33 @@ package p.studio.workspaces.assets.details.palette; +import javafx.application.Platform; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; +import p.packer.events.PackerEventKind; +import p.packer.messages.ApplyPaletteOverhaulingRequest; import p.studio.Container; import p.studio.controls.forms.StudioFormEditScopeChangedEvent; import p.studio.controls.forms.StudioFormMode; import p.studio.controls.forms.StudioFormSection; +import p.studio.events.StudioPackerOperationEvent; import p.studio.events.StudioWorkspaceEventBus; +import p.studio.projects.ProjectReference; import p.studio.utilities.i18n.I18n; import p.studio.workspaces.assets.messages.AssetWorkspaceDetailsViewState; +import p.studio.workspaces.assets.messages.events.StudioAssetLogEvent; +import p.studio.workspaces.assets.messages.events.StudioAssetsRefreshRequestedEvent; import p.studio.workspaces.assets.messages.events.StudioAssetsDetailsViewStateChangedEvent; import p.studio.workspaces.framework.StudioSubscriptionBag; +import java.util.Map; import java.util.Objects; +import java.util.UUID; public final class AssetDetailsPaletteOverhaulingControl extends StudioFormSection { private static final String SECTION_ID = "asset-details.palette-overhauling"; + private final ProjectReference projectReference; private final StudioWorkspaceEventBus workspaceBus; private final StudioSubscriptionBag subscriptions = new StudioSubscriptionBag(); private final AssetDetailsPaletteOverhaulingCoordinator coordinator = new AssetDetailsPaletteOverhaulingCoordinator(); @@ -28,7 +38,8 @@ public final class AssetDetailsPaletteOverhaulingControl extends StudioFormSecti private AssetWorkspaceDetailsViewState viewState; - public AssetDetailsPaletteOverhaulingControl(StudioWorkspaceEventBus workspaceBus) { + public AssetDetailsPaletteOverhaulingControl(ProjectReference projectReference, StudioWorkspaceEventBus workspaceBus) { + this.projectReference = Objects.requireNonNull(projectReference, "projectReference"); this.workspaceBus = Objects.requireNonNull(workspaceBus, "workspaceBus"); body.getStyleClass().add("assets-details-palette-overhauling-body"); content.setFillWidth(true); @@ -149,9 +160,49 @@ public final class AssetDetailsPaletteOverhaulingControl extends StudioFormSecti @Override protected void apply() { - coordinator.apply(); - publishEditScope(workspaceBus, currentScopeKey(), null); - renderSection(); + if (viewState == null || viewState.selectedAssetReference() == null || !coordinator.ready()) { + return; + } + final var selectedPalettes = coordinator.viewModel().selectedFiles().stream() + .map(file -> palettePayload(file.metadata())) + .filter(Objects::nonNull) + .toList(); + if (selectedPalettes.isEmpty()) { + workspaceBus.publish(new StudioAssetLogEvent("palette-overhauling", "Apply failed: no palette was selected.")); + Container.eventBus().publish(new StudioPackerOperationEvent( + UUID.randomUUID().toString(), + PackerEventKind.ACTION_FAILED, + "Palette overhauling apply failed: no palette was selected.", + null, + true)); + return; + } + + final var assetReference = viewState.selectedAssetReference(); + Container.backgroundTasks().submit(() -> { + final var request = new ApplyPaletteOverhaulingRequest( + projectReference.toPackerProjectContext(), + assetReference, + selectedPalettes); + try { + final var response = Container.packer().workspaceService().applyPaletteOverhauling(request); + Platform.runLater(() -> { + if (response.success()) { + coordinator.apply(); + publishEditScope(workspaceBus, currentScopeKey(), null); + renderSection(); + workspaceBus.publish(new StudioAssetsRefreshRequestedEvent(assetReference)); + } else { + workspaceBus.publish(new StudioAssetLogEvent("palette-overhauling", + "Apply failed: " + Objects.requireNonNullElse(response.errorMessage(), "unknown error"))); + } + }); + } catch (Exception exception) { + Platform.runLater(() -> workspaceBus.publish(new StudioAssetLogEvent( + "palette-overhauling", + "Apply failed: " + Objects.requireNonNullElse(exception.getMessage(), "unknown error")))); + } + }); } @Override @@ -180,4 +231,10 @@ public final class AssetDetailsPaletteOverhaulingControl extends StudioFormSecti ? null : "asset-details:" + viewState.selectedAssetReference(); } + + @SuppressWarnings("unchecked") + private static Map palettePayload(Map metadata) { + final Object palette = metadata.get("palette"); + return palette instanceof Map paletteMap ? (Map) paletteMap : null; + } } diff --git a/prometeu-studio/src/main/java/p/studio/workspaces/assets/details/palette/AssetDetailsPaletteOverhaulingCoordinator.java b/prometeu-studio/src/main/java/p/studio/workspaces/assets/details/palette/AssetDetailsPaletteOverhaulingCoordinator.java index ead26ef7..69a713cb 100644 --- a/prometeu-studio/src/main/java/p/studio/workspaces/assets/details/palette/AssetDetailsPaletteOverhaulingCoordinator.java +++ b/prometeu-studio/src/main/java/p/studio/workspaces/assets/details/palette/AssetDetailsPaletteOverhaulingCoordinator.java @@ -27,13 +27,7 @@ public final class AssetDetailsPaletteOverhaulingCoordinator { final List previewTileFiles = details.bankComposition().selectedFiles().stream() .filter(AssetDetailsPaletteOverhaulingCoordinator::supportsPalettePreview) .toList(); - final Map filesByPath = allFiles.stream() - .collect(java.util.stream.Collectors.toMap( - AssetWorkspaceBankCompositionFile::path, - file -> file, - (left, right) -> left, - LinkedHashMap::new)); - final List initialSelected = new ArrayList<>(); + final List initialSelected = initialSelectedPaletteFiles(details, allFiles); final List available = new ArrayList<>(allFiles); available.removeAll(initialSelected); @@ -169,6 +163,29 @@ public final class AssetDetailsPaletteOverhaulingCoordinator { metadata); } + private static List initialSelectedPaletteFiles( + AssetWorkspaceAssetDetails details, + List allFiles) { + final List selected = new ArrayList<>(); + for (Map palette : details.pipelinePalettes()) { + for (AssetWorkspaceBankCompositionFile file : allFiles) { + if (!selected.contains(file) && paletteEquals(palette, nestedMap(file.metadata(), "palette"))) { + selected.add(file); + break; + } + } + } + return List.copyOf(selected); + } + + private static boolean paletteEquals(Map left, Map right) { + if (left == null || right == null) { + return false; + } + return Objects.equals(left.get("originalArgb8888"), right.get("originalArgb8888")) + && Objects.equals(left.get("convertedRgb565"), right.get("convertedRgb565")); + } + @SuppressWarnings("unchecked") private static String paletteHash(AssetWorkspaceBankCompositionFile file) { final Map palette = nestedMap(file.metadata(), "palette"); diff --git a/prometeu-studio/src/main/java/p/studio/workspaces/assets/messages/AssetWorkspaceAssetDetails.java b/prometeu-studio/src/main/java/p/studio/workspaces/assets/messages/AssetWorkspaceAssetDetails.java index f6e3c569..710ac044 100644 --- a/prometeu-studio/src/main/java/p/studio/workspaces/assets/messages/AssetWorkspaceAssetDetails.java +++ b/prometeu-studio/src/main/java/p/studio/workspaces/assets/messages/AssetWorkspaceAssetDetails.java @@ -18,6 +18,7 @@ public record AssetWorkspaceAssetDetails( Map> codecConfigurationFieldsByCodec, List metadataFields, AssetWorkspaceBankCompositionDetails bankComposition, + List> pipelinePalettes, List diagnostics) { public AssetWorkspaceAssetDetails { @@ -29,6 +30,7 @@ public record AssetWorkspaceAssetDetails( codecConfigurationFieldsByCodec = Map.copyOf(Objects.requireNonNull(codecConfigurationFieldsByCodec, "codecConfigurationFieldsByCodec")); metadataFields = List.copyOf(Objects.requireNonNull(metadataFields, "metadataFields")); bankComposition = Objects.requireNonNull(bankComposition, "bankComposition"); + pipelinePalettes = List.copyOf(Objects.requireNonNull(pipelinePalettes, "pipelinePalettes")); diagnostics = List.copyOf(Objects.requireNonNull(diagnostics, "diagnostics")); } } diff --git a/prometeu-studio/src/test/java/p/studio/workspaces/assets/details/bank/AssetDetailsBankCompositionCoordinatorTest.java b/prometeu-studio/src/test/java/p/studio/workspaces/assets/details/bank/AssetDetailsBankCompositionCoordinatorTest.java index 9061fd75..7532d350 100644 --- a/prometeu-studio/src/test/java/p/studio/workspaces/assets/details/bank/AssetDetailsBankCompositionCoordinatorTest.java +++ b/prometeu-studio/src/test/java/p/studio/workspaces/assets/details/bank/AssetDetailsBankCompositionCoordinatorTest.java @@ -112,6 +112,7 @@ final class AssetDetailsBankCompositionCoordinatorTest { files("tile", fileCount, 1024L), List.of(), 0L), + List.of(), List.of()); } @@ -130,6 +131,7 @@ final class AssetDetailsBankCompositionCoordinatorTest { new AssetWorkspaceBankCompositionFile("b.wav", "b.wav", secondSize, 1L, null, Map.of())), List.of(), 0L), + List.of(), List.of()); } diff --git a/prometeu-studio/src/test/java/p/studio/workspaces/assets/details/palette/AssetDetailsPaletteOverhaulingCoordinatorTest.java b/prometeu-studio/src/test/java/p/studio/workspaces/assets/details/palette/AssetDetailsPaletteOverhaulingCoordinatorTest.java index ed117495..0b0a6ba1 100644 --- a/prometeu-studio/src/test/java/p/studio/workspaces/assets/details/palette/AssetDetailsPaletteOverhaulingCoordinatorTest.java +++ b/prometeu-studio/src/test/java/p/studio/workspaces/assets/details/palette/AssetDetailsPaletteOverhaulingCoordinatorTest.java @@ -91,6 +91,7 @@ final class AssetDetailsPaletteOverhaulingCoordinatorTest { Map.of(OutputCodecCatalog.NONE, List.of()), List.of(), new AssetWorkspaceBankCompositionDetails(availableFiles, selectedFiles, 0L), + selectedFiles.stream().map(file -> (Map) file.metadata().get("palette")).toList(), List.of()); } diff --git a/test-projects/main/.studio/activities.json b/test-projects/main/.studio/activities.json index ee64ae7f..fe0a8219 100644 --- a/test-projects/main/.studio/activities.json +++ b/test-projects/main/.studio/activities.json @@ -198,6 +198,206 @@ "message" : "Asset scan started", "severity" : "INFO", "sticky" : false +}, { + "source" : "Assets", + "message" : "7 assets loaded", + "severity" : "SUCCESS", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Asset scan diagnostics updated.", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: bla", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: one-more-atlas", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: ui_atlas", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: one-more-atlas", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: bbb2", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: ui_atlas", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: Bigode", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Asset scan started", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "7 assets loaded", + "severity" : "SUCCESS", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Asset scan diagnostics updated.", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: bla", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: one-more-atlas", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: ui_atlas", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: one-more-atlas", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: bbb2", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: ui_atlas", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: Bigode", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Asset scan started", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "7 assets loaded", + "severity" : "SUCCESS", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Asset scan diagnostics updated.", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: bla", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: one-more-atlas", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: ui_atlas", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: one-more-atlas", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: bbb2", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: ui_atlas", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: Bigode", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Asset scan started", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "7 assets loaded", + "severity" : "SUCCESS", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Asset scan diagnostics updated.", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: bla", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: one-more-atlas", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: ui_atlas", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: one-more-atlas", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: bbb2", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: ui_atlas", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Discovered asset: Bigode", + "severity" : "INFO", + "sticky" : false +}, { + "source" : "Assets", + "message" : "Asset scan started", + "severity" : "INFO", + "sticky" : false }, { "source" : "Assets", "message" : "Excluded asset in build: ui/sound", @@ -2298,204 +2498,4 @@ "message" : "Included asset in build: bigode", "severity" : "SUCCESS", "sticky" : false -}, { - "source" : "Assets", - "message" : "7 assets loaded", - "severity" : "SUCCESS", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Asset scan diagnostics updated.", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: bla", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: one-more-atlas", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: ui_atlas", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: one-more-atlas", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: bbb2", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: ui_atlas", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: Bigode", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Asset scan started", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Excluded asset in build: bigode", - "severity" : "SUCCESS", - "sticky" : false -}, { - "source" : "Assets", - "message" : "7 assets loaded", - "severity" : "SUCCESS", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Asset scan diagnostics updated.", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: bla", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: one-more-atlas", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: ui_atlas", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: one-more-atlas", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: bbb2", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: ui_atlas", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: Bigode", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Asset scan started", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Included asset in build: bigode", - "severity" : "SUCCESS", - "sticky" : false -}, { - "source" : "Assets", - "message" : "7 assets loaded", - "severity" : "SUCCESS", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Asset scan diagnostics updated.", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: bla", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: one-more-atlas", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: ui_atlas", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: one-more-atlas", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: bbb2", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: ui_atlas", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: Bigode", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Asset scan started", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Excluded asset in build: bigode", - "severity" : "SUCCESS", - "sticky" : false -}, { - "source" : "Assets", - "message" : "7 assets loaded", - "severity" : "SUCCESS", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Asset scan diagnostics updated.", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: bla", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: one-more-atlas", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: ui_atlas", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: one-more-atlas", - "severity" : "INFO", - "sticky" : false -}, { - "source" : "Assets", - "message" : "Discovered asset: bbb2", - "severity" : "INFO", - "sticky" : false } ] \ No newline at end of file diff --git a/test-projects/main/assets/ui/atlas2/asset.json b/test-projects/main/assets/ui/atlas2/asset.json index d15d0463..ce741814 100644 --- a/test-projects/main/assets/ui/atlas2/asset.json +++ b/test-projects/main/assets/ui/atlas2/asset.json @@ -9,6 +9,15 @@ "codec_configuration" : { }, "metadata" : { "tile_size" : "32x32" + }, + "pipeline" : { + "palettes" : [ { + "originalArgb8888" : [ -265674, -1736296, -13905598, -11518505, -14439577, -2467509 ], + "convertedRgb565" : [ 65414, 58387, 11912, 20986, 9548, 56009 ] + }, { + "originalArgb8888" : [ -265674 ], + "convertedRgb565" : [ 65414 ] + } ] } }, "preload" : {