DataDecorator
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:
| Value | Compiled in | Consequence |
|---|---|---|
| Nakama port | 7350 | Coolify routes on 443, so the request 404s |
| Server key | defaultkey | a generated key gives 401 … Server key invalid |
| Fallback host | nkdev2.humuus.de | harmless — 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
| Key | Type | Default | Description |
|---|---|---|---|
data.url | string | "" | Base URL of the Data Decorator web app that will be iframed for players. |
data.gameSettings.RoundLength | number | 180 | Seconds per round inside the minigame. |
data.gameSettings.maxRounds | number | 4 | Maximum rounds the session should play. |
data.gameSettings.ProgressDisplayDuration | number | 5 | Seconds to show inter-round progress. |
data.gameSettings.targetScore | number | 1000 | Target score for the session. |
data.gameSettings.persona | number | 0 | Optional 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 = 30seconds. - Start signal once ready: sends
{ code: 1, msg: { gameSettings } }to the sub-match. - Polling every
workshop_DataDecorator_SignalInterval = 30seconds: sends{ code: 2 }to request an update and setsgameStatefrom 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
ddMatchIdvia the node’s response toJoinMatch. - 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.
LocalStorageKeys.DEVICE_IDmust 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 }
}
]