Skip to main content

DataDecorator

Out of service

This node no longer works, and reviving it needs a change to the Unity build in techagogics/DataDecoratorWebGl — nothing on the humuus side can fix it. The workshop data_decorator.json cannot run.

humuus embeds the game in an iframe and hands it the backend as query parameters:

https://dd.humuus.de/?humuus=true&matchid=…&deviceid=…&host=…&https=…

The shipped build reads host, https, matchid and deviceid — and nothing else. Three values it needs are compiled in, and humuus cannot influence any of them:

ValueCompiled inConsequence
Nakama port7350Coolify routes on 443, so the request 404s
Server keydefaultkeya generated key gives 401 … Server key invalid
Fallback hostnkdev2.humuus.deharmless — host is the one parameter it does read

Verified by extracting the strings from Build/DataDecorator Builds.data.br, and by passing &port=443, which changed nothing.

It used to work because the previous deployment exposed Nakama on port 7350 through a hand-maintained Traefik file, and because the server key was still at its default. Both went away when production moved to Coolify — the first deliberately, the second as part of taking the leaked credentials out of the repository.

What reviving it takes: read port and a server key from the query string in the Unity client, then pass NEXT_PUBLIC_NAKAMA_SERVER_PORT and NEXT_PUBLIC_NAKAMA_SERVER_KEY from DataDecoratorNode.tsx, which already hold the right values per environment. Two lines on each side, plus a Unity rebuild and a redeploy of dd.humuus.de.

Approaches that were tried and do not work are recorded in Deployment, so nobody spends the afternoon again.

Integrates the external Data Decorator minigame, showing a live host scoreboard while players join and play inside an embedded page.

Example structures​

Basic​

{
"type": "DataDecorator",
"data": {
"url": "https://dd.humuus.de/",
"gameSettings": {
"RoundLength": 180,
"maxRounds": 4,
"ProgressDisplayDuration": 5,
"targetScore": 1000
}
}
}

Full (with persona)​

{
"type": "DataDecorator",
"data": {
"url": "https://dd.humuus.de/",
"gameSettings": {
"RoundLength": 120,
"maxRounds": 5,
"ProgressDisplayDuration": 8,
"targetScore": 1500,
"persona": 2
}
}
}

Properties​

KeyTypeDefaultDescription
data.urlstring""Base URL of the Data Decorator web app that will be iframed for players.
data.gameSettings.RoundLengthnumber180Seconds per round inside the minigame.
data.gameSettings.maxRoundsnumber4Maximum rounds the session should play.
data.gameSettings.ProgressDisplayDurationnumber5Seconds to show inter-round progress.
data.gameSettings.targetScorenumber1000Target score for the session.
data.gameSettings.personanumber0Optional persona preset for the minigame.

What players and host see​

Players (participant view) An iframe loads the external app. The node builds the URL with required query parameters:

{url}?humuus=true
&matchid={ddMatchId}
&deviceid={LocalStorageKeys.DEVICE_ID}
&host={NEXT_PUBLIC_NAKAMA_SERVER_HOST}
&https={NEXT_PUBLIC_NAKAMA_USE_SSL}

Host A compact scoreboard card shows the current round and four progress metrics as percentages:

  • Daten Sortieren → pM1
  • Daten minen → pM2
  • Daten analyzieren → pM3
  • Daten Visualizieren → pM4

The host view also shows the current round as round / maxRounds.

Lifecycle and server loop​

  • init() creates a dedicated sub-match for the minigame: ddMatchId = nk.matchCreate(GAMETYPE_dataDecorator, { creator, joincode: "-1", humuus: true }).
  • Start gating in loop() waits until either all expected players are present or a join timeout is reached. Timeout: workshop_DataDecorator_JoinMatchWaitTime = 30 seconds.
  • Start signal once ready: sends { code: 1, msg: { gameSettings } } to the sub-match.
  • Polling every workshop_DataDecorator_SignalInterval = 30 seconds: sends { code: 2 } to request an update and sets gameState from the reply, then pushes the new state to the host only.

Default node sound is disabled for this node.

Client protocol (workshop node opcodes)​

The React component sends an immediate join request on mount:

const WorkshopNodeOpcode_DataDecorator = { JoinMatch: 0, GameState: 1 };
props.sendDataToNode(WorkshopNodeOpcode_DataDecorator.JoinMatch, {});

On the server, the node maps these to match-layer opcodes:

  • JoinMatch (200): server replies to the requester with { ddMatchId }.
  • GameState (201): reserved (no action in the shown code).

Data model​

Validated state sent/stored by the node

interface DataDecoratorNodeState {
ddMatchId: string;
ddMatchStarted: boolean;
ddMatchTimeWaited: number;
url: string;
gameSettings: {
RoundLength: number;
maxRounds: number;
ProgressDisplayDuration: number;
targetScore: number;
persona: number;
};
gameState: {
pM1: number;
pM2: number;
pM3: number;
pM4: number;
round: number;
};
}

Incoming config

interface DataDecoratorNodeState_Storage {
url: string;
gameSettings: {
RoundLength: number;
maxRounds: number;
ProgressDisplayDuration: number;
targetScore: number;
persona: number;
};
}

Message envelope used with the sub-match

interface Workshop_DataDecorator_MessageObject {
code: number; // 1 start, 2 poll
msg: any;
}

Behavior details​

  • Join readiness compares the sub-match size to workshop_connectedPlayers(state, false) or uses the 30s fallback timer.
  • Host receives periodic updates only after the sub-match is started.
  • Players load the external app once they receive ddMatchId via the node’s response to JoinMatch.
  • The iframe is full-viewport in the player view and responsive.

Use cases​

  • Drop-in minigame between workshop sections with a live progress board for the facilitator.
  • Team-based or class-based rounds where the external app handles the heavy logic and the workshop shell handles orchestration.
tip
  • LocalStorageKeys.DEVICE_ID must be present; ensure your device ID is initialized before entering the node.
  • The external app must honor the query parameters and connect to the given Nakama host/match. Cross-origin settings need to allow embedding.
  • If fewer players arrive than expected, the node still starts after 30 seconds to avoid deadlock.
  • Host labels in the scoreboard are currently German; adapt if you need localized UI.

Integration example​

[
{
"type": "Slide",
"data": {
"headline": "Mini-Game Time",
"text": "Let’s play Data Decorator!"
}
},
{
"type": "DataDecorator",
"data": {
"url": "https://dd.humuus.de/",
"gameSettings": {
"RoundLength": 150,
"maxRounds": 3,
"ProgressDisplayDuration": 6,
"targetScore": 1200,
"persona": 1
}
}
},
{
"type": "Scoreboard",
"data": { "showPodium": false }
}
]