Skip to content

Releases: redcraft-org/RedCraftChat

RedCraftChat 0.2.10-SNAPSHOT

Choose a tag to compare

@github-actions github-actions released this 02 Sep 11:47
Mask whole words, see through colour codes, and reject invented tokens

Three things wrong with the masking that shipped in 0.2.9, found by
reading it back adversarially rather than by anyone reporting them.

The one that mattered: a hologram writes KingdomHills as
§3§lKingdom§b§lHills, so the literal name is nowhere in the string and
the mask never fired. The model saw two ordinary words, translated
both, and French word order handed back CollinesRoyaume, which is the
bug this was meant to fix. Names are matched against a colour stripped
copy now and hidden as a span of the original, so the codes inside come
back exactly as they were.

The one that would have been worse: names matched as bare substrings.
IRS ships in the default protected-names and lives inside FIRST, so
"FIRST JOIN REWARD" went to the model as "F{0}T JOIN REWARD". Matching
is whole words now. A short name inside a longer one keeps only the
prompt rule, which is what it had before.

And restore only checked the tokens it owned, so a token the model
invented walked into player chat and got cached there. Masking only
runs on text that had no placeholder to begin with, so anything left
over afterwards is not ours and the original goes out instead.

Also stopped hiding a server id that is spelled like an ordinary word.
museum is listed verbatim so /server museum keeps working, but hiding
it left "the museum" sitting in English mid sentence.

RedCraftChat 0.2.9-SNAPSHOT

Choose a tag to compare

@github-actions github-actions released this 02 Sep 00:51
Hide protected names from the translator instead of asking it nicely

The museum holograms came back reading CollinesRoyaume again, which is
the second time, and nothing had regressed: the names were in
protected-names, the config was right and the plugin was current. The
protection was a sentence in the system prompt asking the model to copy
those names exactly, and a request is something that works most of the
time. Hologram lines are short and carry almost no context, and
KingdomHills is two ordinary English words stuck together, which is
close to the worst case for asking.

The names now go behind {0} placeholders before the request is built
and come back after, so the model is translating a sentence with a hole
in it rather than being trusted with a proper noun. It cannot translate
a string it never receives.

If a placeholder does not come back, the original text is sent
untranslated, the same way the interactive restore already fails
closed. An English hologram is a much smaller problem than a French one
naming a world nobody can visit.

A line that is only a name now skips the request entirely, which is
most hologram titles. The prompt rule stays for the cases masking steps
around, like a message that already contains a placeholder of its own.

RedCraftChat 0.2.8-SNAPSHOT

Choose a tag to compare

@github-actions github-actions released this 01 Sep 02:59
Let the interceptor tests pin the flag-off world explicitly

The default flip left three tests asserting flag-off behaviour against
a flag-on ambient, which is exactly the red build the previous commit
shipped. setUp forces the historical world for the whole class and the
flag-on tests keep setting true themselves, so the suite no longer
depends on what the shipped default happens to be.

RedCraftChat 0.2.7-SNAPSHOT

Choose a tag to compare

@github-actions github-actions released this 01 Sep 01:39
Translate messages with clickable parts, keeping every event

Interactive messages were always forwarded untranslated, because the
pipeline flattens a component tree to legacy text and clicks, hovers and
insertions do not survive that. A French player got the menu, in
English, rather than a menu whose buttons had quietly died.

ComponentTemplate is the NumericTemplate idea applied to subtrees. Every
event-carrying subtree is lifted out before serialization and its
visible text left inline between paired markers, so one provider call
translates the sentence and the button labels together, with full
context. The events never travel: they wait on the proxy, keyed by
marker name, and are reattached to the translated label afterwards. A
provider cannot corrupt a click, because it never sees one, which is
also why this beats handing the model the whole component as JSON: that
shape sends the events through the model, costs several times the
tokens, and fragments the cache.

Both marker halves match the placeholder pattern the tokenizing
providers already protect, and the Claude prompt names the pair rule.
Restore validates string-level: every pair present exactly once, each
open before its close, no nesting, no interleaving, nothing foreign.
Any violation forwards the original untouched, with a WARN as the
canary to watch before the default ever flips. Softer failures degrade
per pair: an emptied or swollen label keeps its original words while
the sentence translation survives, and a label made of several styled
runs lends the sentence its context but is restored verbatim rather
than guessing how the translation distributes across the runs.

Style survives two ways. The resolved style at each lift point wraps
the restored subtree, so a label inheriting gold from its parent is
still gold under the style-empty rebuilt root, and click, hover and
insertion ride that same wrapper since Adventure keeps all three in
Style. Segments after a pair are re-prefixed with the active legacy
colour state, because legacy formatting is stateful and the split
severed it.

Hover-only runs are lifted too, which fixes a latent bug in passing:
their hover used to be silently destroyed and replaced by the
original-text hover. That hover now attaches to translated segments
only, never to a button, so buttons keep their own hover or none. And
a message that is nothing but a button becomes translatable for the
first time, since its label finally counts as words.

upstream-translate-interactive defaults off for exactly one release as
a config-only kill switch while it soaks; labels-in-context is the only
enabled behaviour, so what soaks is what production will run.

RedCraftChat 0.2.6-SNAPSHOT

Choose a tag to compare

@github-actions github-actions released this 01 Sep 01:10
Translate action bars, both wire forms

The action bar arrives two different ways and they are different packets:
the dedicated SET_ACTION_BAR_TEXT that Adventure's sendActionBar
produces, and system chat with the overlay flag, which is how CMI writes
its. The chat interceptor only ever saw the second, which is why the
elytra speed bar came out translated while the museum's "Use /musehub to
quit" loop and the parkour HUD never did.

ActionBarTranslator handles both, and is deliberately not built on the
chat buffer. Cancelling and holding is what multi-line menus need and
exactly what a surface repainting twenty times a second cannot afford,
so this follows the hologram pattern instead: rewrite in place on the
netty thread when the cache has the answer, pass the original through
and warm the cache when it does not. The first repeat after a miss is
translated; nothing is held or reordered, and no corrective resend,
which would reset the fade timer and double-flash text that corrects
itself one repeat later anyway.

Counters are what make the cache work. NumericTemplate collapses every
frame of a timer onto one template, the template is what gets translated
and cached, and each frame injects its own numbers into the cached
translation. A twenty-hertz HUD costs one provider call ever, and a
frame is never shown carrying a placeholder: the store path refuses
translations that lost one, and the rewrite path refuses output that
still contains one, which covers a provider inventing an extra.

A per-player churn guard counts cache misses, not text changes, so a
cyclic animation stops costing once the cache has seen a full cycle
where a change counter would trip forever. Only new provider spending
goes quiet; cache hits keep rewriting.

Overlay system chat moves out of the interceptor's buffer behind the
same flag, which also stops it paying the grouping delay and a scheduler
hop per frame. translate-action-bars off restores the old behaviour
exactly.

Clients before 1.19.1 are documented as unsupported for this and for
system chat: they receive the legacy chat packet nothing listens for,
and thirty days of logs show no real player below 1.21.5.

RedCraftChat 0.2.5-SNAPSHOT

Choose a tag to compare

@github-actions github-actions released this 31 Aug 12:13
Keep the rank on the name in the leave message

A player joined as Admin>lululombard and left as a plain yellow
lululombard. Two listeners answer DisconnectEvent: one drops the cached
display name, and it does so immediately, while the message builder hands
its work to the scheduler. The lookup therefore ran after the eviction
and fell back to the bare username, which then took the colour of the
sentence around it.

The name is read in the handler's constructor now, while the event is
still on the wire, rather than when the task eventually runs. The join
path never had the bug because nothing is tearing state down at that
moment.

Also restores yellow after the name, which the join line already did and
this one did not, so the rank's colour no longer bleeds into the rest of
the sentence.

RedCraftChat 0.2.4-SNAPSHOT

Choose a tag to compare

@github-actions github-actions released this 31 Aug 01:25
Let names that are not servers be protected from translation too

The museum's world names went out translated: KingdomHills became
RoyaumeCollines, Freebuild became Construction libre. Both read well and
name a world that does not exist, next to a command that still wants the
original.

Server names were already protected, by listing them in the prompt as
proper nouns to copy. Nothing covered names the plugin cannot enumerate,
which is every world, map and build on the network. protected-names is
that list, and translatable-server-names remains the escape hatch for a
word that turns out to be ordinary after all.

It is a blunt tool and the config says so: a word listed there is
verbatim in every sentence, so adding Plot costs its translation
everywhere, not only on the sign it was added for. The seeded list keeps
to the distinctive halves.

The tests around this were reading three static fields that no test
reset, so adding one that populated protectedNames broke an unrelated
assertion further down the file. They are reset in setUp now, by
replacement rather than clear(), since some of them assign an immutable
list.

RedCraftChat 0.2.3-SNAPSHOT

Choose a tag to compare

@github-actions github-actions released this 29 Aug 20:19
Stop a message sent just before a server switch from kicking the player

/hub disconnected the client with "unknown packet id 121". The transfer
itself worked, and the drop came a second later, which is the shape of a
packet written into a connection that has moved on.

System messages are not sent straight through here. The original is
cancelled and a copy is written back after the chat grouping window, and
a player can leave PLAY inside that window by switching servers or
quitting. A play packet written to a connection already in configuration
is decoded against the wrong table, and the client does not recover.

So the delayed write now drops anything whose flush lands outside PLAY.
That guard is not about /servers: any message sent shortly before a
switch or a quit could do this, it just took something that reliably
pairs a message with an immediate switch to make it show. getEncoderState
rather than getConnectionState, because the latter throws while the
encoder and decoder disagree, which is the moment being guarded.

The "sending you to X" line goes with it. It was the trigger, and a
message nobody would have read, since the screen changing says it sooner.

Also aliases the servers people actually ask for by name.

RedCraftChat 0.2.2-SNAPSHOT

Choose a tag to compare

@github-actions github-actions released this 29 Aug 19:44
Add a server selector, and a default server to land on

/servers opens a dialog of the backends, with a way straight back to the
one you came from at the top, and /servers <name> skips the dialog since
an argument means the player already knows where they are going. The
aliases follow: /hub and /s now run it rather than the proxy's /server.

Transfers go through createConnectionRequest rather than by running
/server for the player. That command lives on the proxy, this code is
already on the proxy, and going through it would mean handing the work
to a backend to hand straight back.

The setting underneath it answers a different question: where to put
someone at login. Three answers share one column, so "last server" is
stored as @last, spelled with a character a server id cannot contain,
because a network is free to register a backend called "last" and that
must not silently become a rule. With nothing set, the tick falls on
whatever the proxy's try list would have chosen, read from
getAttemptConnectionOrder rather than assumed to be the hub: it is the
hub today, but that is a fact about one config file, and a screen that
states it out loud should not be what goes stale when the file changes.

Two columns were added to rcc_player_preferences ahead of this, since
norm names every column in its SELECT and a missing one breaks reads for
every player rather than only writes.

The network is named from its own config key rather than borrowed from
the Discord activity string, so changing what Discord shows does not
reword an unrelated screen.

RedCraftChat 0.2.1-SNAPSHOT

Choose a tag to compare

@github-actions github-actions released this 29 Aug 19:08
Let Bedrock use the dialogs, and delete the Cumulus copies

The Bedrock forms existed because I concluded Bedrock could not render a
dialog. That was wrong, and generalised from a real finding about chat:
Geyser does strip click and hover from chat components, but dialogs
travel a different packet path with a dedicated translator. Geyser
2.11.2 has JavaShowDialogGameTranslator, builds a CustomForm or a
SimpleForm from the dialog depending on whether it carries inputs,
handles text, boolean, single-option and number-range inputs, and sends
ServerboundCustomClickActionPacket back, which is the packet
DialogClickListener already reads. MultiActionDialog, the type sent
here, extends the class that does the form building.

The dialog appeared to do nothing on Bedrock at the time, which seemed
to confirm it. That was two bugs fixed since: pause set alongside
DialogAction.NONE, which rendered nothing, and an input key the client
rejected, which kicked it. Both broke Java too.

So the parallel implementation goes, along with the Floodgate dependency
that only it used. One definition per screen means the two can no longer
drift, which they did repeatedly: the recipient labels, the reply
wording, the arrival dates and the sent-to separator each had to be
applied twice.

Bedrock is still kept off the in-world panel. That is display entities
driven by interaction packets, a different question, and unverified.
Below the dialog it now falls to chat rather than to the panel.

Routing decisions are logged with their inputs, because a route on its
own never says which capability reported what.