AgentMap.Instance() and AgentChatLog.Instance() can return null during
zone transitions. Capture pointers into locals and short-circuit the
FlagMarkerCount/LinkedItem deref when null so the entries are correctly
greyed out without faulting. Add Activate/ActivatePos after each append
so the input box regains focus and the caret lands after the token,
matching the SymbolPicker and AutoComplete insert paths.
Guard _childScrolledUp writes behind updateScrollState param so pop-out
windows no longer contaminate the main window's scroll state. Widen the
honorific title slot budget when the scroll button is visible, fix stale
comment, and apply csharpier formatting.
Drop the three-attempt floating overlay entirely. The button now lives in
the chat header toolbar (DrawScrollToBottomToolbarButton), visible only when
the user is scrolled above the live end. Toolbar layout: honorific slot,
scroll button, pop-out button flush-right -- pop-out position unchanged.
Button drawn in the parent window over the ##chat2-messages child was never
clickable: ImGui resolves g.HoveredWindow to the child for that screen rect, so
ItemHoverable rejects any item submitted in the parent. A top-level Begin/End
window is a sibling in the window list and wins the hit-test for its own rect.
ownerId parameter keeps the window name distinct between the main window and
each pop-out, preventing Begin/End collisions when both render in the same frame.
The button was drawn inside the ##chat2-messages child via SetCursorPos,
which inflated ContentSize.y / ScrollMaxY each frame (causing positional
drift) and was clipped by the scrollbar's inner clip rect (causing right-
edge cutoff). Move it to the parent window using screen-space coordinates
captured before the child opens; the scroll state is cached inside the
child while GetScrollMaxY/Y still refer to the child's scroll context.
Bumps csproj, yaml, repo.json, CHANGELOG, ROADMAP and README in
lock-step to 1.5.4. Forge-post DE-body added with the Polish & Motion
versionsnatur. Slim-rule applied to the yaml and repo.json changelog
blocks (keeps v1.5.4 + v1.5.3 + v1.5.2 + v1.5.1, drops v1.5.0).
A csharpier reflow of two v1.5.4 source files (ChatLogWindow,
HellionStyle) is folded in. preflight.sh blocks A-F all green.
ThemeCrossfadeSelfTestStep walks Switch -> crossfade-observed ->
mid-crossfade-switch -> crossfade-end -> restore using
TryGetActiveCrossfade, returns Waiting frame-by-frame and Pass after
the restore concludes. The mid-switch phase fires a second Switch
within ~100ms of the first observed crossfade and asserts the lerped
value is neither identity-from nor identity-to, exercising the
ArmCrossfade mid-flight-origin override.
QuickPickerSelfTestStep verifies the three new resource strings, the
built-in theme floor (>=10), and Config.Tabs non-empty.
Sidebar icons ease from 40% to 100% alpha on hover-in via FrameLerp
plus ApplyAlpha. Card-mode borders aggregate row-hover per tab and
lift the border alpha by up to ~+0x70 across every row in that tab.
borderColorAbgr moves into the loop so the per-iteration boost can
apply. ReduceMotion snaps both paths instantly.
Card-hover detection uses IsMouseHoveringRect over the row bounds --
IsItemHovered would only see the 2px spacer dummy below each row.
FrameLerp.Smooth is the framerate-independent smoothing path -- a
Umbra-style v += (target - v) * factor with the factor clamped to 1
so a stalled frame snaps cleanly instead of overshooting. Tab gets
two NonSerialized fields (_hoverAlpha, _cardHoverAlpha) that the
v1.5.4 render loops drive.
Palette button left of the cog opens a two-section popup. The themes
section enumerates AllBuiltIns + AllCustom; the tabs section
enumerates Config.Tabs. The active entry gets a leading check-glyph,
inactive rows a same-width blank so labels stay aligned. Click
selects without closing the popup (DontClosePopups).
Theme click triggers the PM-1 crossfade via ThemeRegistry.Switch;
tab click routes through ChangeTab so LastActivityTime stays
consistent with the sidebar and top-bar click paths.
The header input-width reservation now counts the new button plus
the per-button SameLine spacing -- the old formula dropped the
spacing term and overflowed the row once a third button appeared.
Five new keys across the EN source plus 24 locale variants (DE plus
23 AI-assisted, each carrying the pending-review marker): the header
quick-picker tooltip and two section headers, plus name and
description for a new ReduceMotion checkbox.
ReduceMotion was a config field with no UI -- the checkbox lands in
the Theme & Layout tab's window-style section. Designer.cs hand-edited
as a v1.5.4 block matching the v1.4.8 convention.
Switch picks a lerped AbgrCache during the 300ms crossfade window
(ReduceMotion bypass keeps the snap path). Plugin-load init path
switches to SwitchSilent so opening the plugin no longer fades from
the default theme. WindowBg/ChildBg RGBA path stays bound to the
user's per-window opacity override and never fades.
PushGlobal takes the ThemeRegistry as a parameter -- it is an instance
member on Plugin, not static, so the single Plugin.Draw call-site
threads it through alongside the active theme.
Three new private fields plus TryGetActiveCrossfade entry-point, plus
SwitchSilent variant for the plugin-load init path. ArmCrossfade
captures a value-copy of the active AbgrCache and stamps TickCount64;
mid-crossfade Switch composes the current lerped state as the next
fade origin so back-to-back theme switches stay smooth.
Same-slug Switch is a no-op (no identity-crossfade).
Plugin.cs:937 only pushed RegularFont when Config.FontsEnabled was true.
FontsAndColours.cs:50 forces FontsEnabled=false whenever UseHellionFont is
enabled (to hide the chooser UI), so the bundled-font path was silently
dead and the FFXIV Axis game-font took over. Exo 2 looked "almost right"
because it overlaps Axis on basic Latin, so the regression went unnoticed
for the entire v1.5.x series.
The fix routes RegularFont through draw whenever either FontsEnabled or
UseHellionFont is on. First-frame HITCH dropped from ~74 ms to ~20 ms
median (5-reload Linux/Wine sample 17.9-23.6 ms) as a side effect — the
v1.5.1 "too optimistic" defer-pattern hypothesis was actually a symptom
of this bug, not bad math.
Font-stack overhaul on top:
- Inter Light (Static 18pt-Light, 343 KB, SIL OFL 1.1) replaces Exo 2 as
the bundled font. Inter ships full Latin Extended-A/B, Greek polytonic
and Cyrillic Supplement coverage.
- NotoSansCjkRegular added as a third merge layer for Hangul,
Simplified-Chinese-specific Han glyphs, and CJK fallbacks the FFXIV
Japanese font does not ship.
- Two new ExtraGlyphRanges flags (LatinExtended, Greek) implemented via
AddChar pair lists in SetUpRanges.
- Settings.Apply auto-activates the matching ExtraGlyphRanges flag on
language change. Plugin.LoadAsync runs a one-shot migration that ORs
in the required flag for an already-selected language.
- ExtraGlyphRanges CollapsingHeader reachable regardless of
UseHellionFont (was hidden in the early-return branch).
- New WarningText below the language combo: FFXIV's chat engine only
fully supports EN/DE/FR/JA. Other scripts render in the HellionChat
UI but may garble in in-game chat input/send.
Localisation wave (originally a FR-only cycle):
- 24 selectable UI languages. LanguageOverride enum gains 10 new locales
plus 3 previously commented-out (Italian, Korean, Norwegian with ISO
code `nb` instead of `no`). All new values append to keep existing
user-config integer serialisation stable.
- Resource bundle split: HellionStrings.resx (24 locales, 328 keys) for
fork-added strings, Language.resx (24 locales, 456 keys) for the
ChatTwo-Crowdin-heritage. 4 post-sync Crowdin keys backfilled into
13 legacy locales with per-key AI-assisted comment marker.
- Em-dash sweep on EN source plus 18 translations. Russian and Ukrainian
keep their typographic norm.
Old HellionFont.ttf + HellionFont-OFL.txt removed; Inter-Light.ttf +
Inter-OFL.txt take their place. Configuration field UseHellionFont keeps
its name for backwards-compat. Migration v17 stays.
Split fork-added keys into a dedicated HellionStrings resource bundle separate
from the Language.*.resx Chat-2 Crowdin heritage.
- Add HellionStrings.resx (EN source, 328 keys) and HellionStrings.Designer.cs
- Add 22 HellionStrings.<code>.resx variants: ca, cs, da, de, es, fi, fr, hu, it,
ja, ko, nb, nl, pl, pt-BR, pt-PT, ro, ru, sv, tr, uk, zh-Hans, zh-Hant
- Add matching Language.<code>.resx siblings for the new locales with the
Hellion Forge maintainer header
- FR pass: align labels with the rest of the UI
(Confidentialité, Visualiseur, Violet indigo)
Preflight Block E (`dotnet csharpier check`) flagged two reflows
in the v1.5.2 code: the ForgeBronzeDim Vector4 constant needed
multi-line form, and a handful of switch arms / long Plugin.Config
chains in WizardStateSmokeStep needed line-breaks at csharpier's
print-width. Pure formatting — zero functional change. Block D
build stays clean, Block E now passes.
Bilingual layout: DE in this file, EN extracted by forge-announce.yml
from HellionChat.yaml changelog block. Body covers the four-step
wizard rewrite, the new Roleplay profile, the surfaced power
settings, the staged-commit + test-hint pattern, the
WizardLastShownVersion re-show-once mechanism for existing users
and the under-the-hood test additions. Subtitle 54 chars,
versionsnatur 8 chars, embed sum (forge body + en-yaml + footer)
4158 chars — all under the workflow caps (60 / 40 / 5500).
Fixes two minor copy-paste artefacts in the v1.5.2 CHANGELOG block:
the duplicate trailing "EUPL-1.2." right after the Based-on footer,
and a stray German "Optik" tab name in the power-settings list
(the settings tab is "Appearance" in EN, the German label only
appears in the localised UI). Yaml / repo.json / ROADMAP / README
already used the right wording.
Bumps csproj Version, repo.json AssemblyVersion/TestingAssemblyVersion
plus the three DownloadLink* URLs, yaml + repo.json changelog blocks
(slim-rule: v1.5.2 + v1.5.1 + v1.5.0 + v1.4.10 retained, v1.4.9
trimmed to the Full history footer link), docs CHANGELOG long-form
block, ROADMAP v1.5.2 marked complete and v1.5.3 set as next cycle
(FR localisation with Hezcal native-speaker review), README status
strings plus moved pre-v1.5.2 history. Changelog includes the
in-cycle UI shrink + Fox-Banner-TreeNode smoke fix and the
WizardLastShownVersion re-show-once mechanism for existing users.
Bestehende User haben FirstRunCompleted=true vom alten Single-Page
Wizard und würden den neuen Multi-Step-Flow nie zu sehen bekommen.
Neues Config-Feld WizardLastShownVersion (Default leer) trägt die
Version, deren Wizard zuletzt gezeigt wurde. Plugin.LoadAsync
vergleicht gegen die Konstante WizardReshowVersion ("1.5.2") und
setzt FirstRunCompleted einmalig zurück, wenn die Werte abweichen.
SaveConfig sofort danach, damit ein Pre-Finish-Crash die Re-Show
nicht endlos wiederholt. Künftige Cycles bumpen die Konstante nur
wenn der Wizard wirklich umstrukturiert wird.
Smoke feedback v1.5.2 R1: the 900x560 default size dominated the
screen and the centred MonoFont fox silhouette filled the welcome
step. Default size drops to 720x480, MinimumSize to 600x400, so
the wizard fits comfortably on a sub-monitor and still leaves the
power-settings step readable when shrunk. Step 1 wraps the banner
in a folded TreeNode (label "Hellion Forge", same anchor pattern
the v1.5.1 wizard used) so the onboarding copy stays the primary
focus and users opt into the silhouette explicitly.
Variant 1 walks the FirstRunWizard state machine through Step 1 →
4 and commits with no pending values to verify the no-op
write-back path. Variant 2 picks Roleplay on Step 2, skips Step 3,
commits, and asserts LoadPreviousSession /
FilterIncludePreviousSessions stayed on their pre-test value —
pinning the null-semantics from Spec Z.176. ApplyRoleplay would
overwrite six privacy / retention fields, so the step snapshots
them before Variant 2 and CleanUp() restores them, keeping the
self-test idempotent across /xlperf runs. Catches state-machine
throws and CommitPending NREs that would otherwise surface as a
hard plugin crash during Finish ✓ clicks. Runs alongside the
existing three FontManager / ThemeSwitch self-test steps.
Multi-step navigation (Welcome → Privacy → Power Settings → Done)
with a nested WizardState holding nullable Pending* fields. Profile
picker becomes a 2x2 grid covering all four privacy profiles
(PrivacyFirst, Casual ★ recommended, Roleplay new, FullHistory).
Power-settings step surfaces six previously-hidden Configuration
fields (LoadPreviousSession, FilterIncludePreviousSessions,
AutoTellTabsHistoryPreload, UseCompactDensity, PrettierTimestamps,
Theme) without introducing new ones. ApplyRoleplay mirrors the
existing Apply* methods, CommitPending writes only the non-null
fields back so skipping a step preserves existing config. OnClose
docstring updated to reflect the actual code path (both Decide-Later
and Finish set FirstRunCompleted = true, the wizard does not reopen).
Thirty-two new bilingual resource keys covering all four wizard
steps: titles, section headings, control labels, navigation, the
new Roleplay profile, the staged-summary template strings, the
'Decide later' multi-step skip label plus its dedicated tooltip.
Existing Wizard_Cancel_Label and Wizard_Cancel_Tooltip stay
untouched for legacy reopen paths.
Adds RoleplayWhitelist (PrivacyFirst + Say + both emote types) and
RoleplayRetentionOverrides (Say 30d, emotes 90d). Shout/Yell and
Novice Network stay out — public-distance noise from strangers
is not story content. Whitelist + overrides are IReadOnlySet /
IReadOnlyDictionary with pure-helper type footprint, so the Build
Suite can pin them without touching Dalamud.
- csproj <Version> 1.5.0 to 1.5.1; <None Include="images\**"> now
excludes the source-only ASCII study folder so the deploy stays
clean
- yaml + repo.json changelog block prepended with the v1.5.1 entry,
v1.4.8 trimmed out per the slim rule (three to four versions in
the manifest cache, older history lives on the Gitea release page)
- repo.json AssemblyVersion + TestingAssemblyVersion bumped to
1.5.1.0, three DownloadLink* URLs point at v1.5.1
- docs/CHANGELOG and docs/ROADMAP gain the v1.5.1 entry; ROADMAP
Next-Cycle slot moves to v1.5.2 First-Run-Wizard rework
- README status sections updated, the previous v1.5.0 paragraph
kept under a "Project status (pre-v1.5.1, kept for context)"
heading
- Forge-post .github/forge-posts/v1.5.1.md added, DE body honest
about the HITCH-win miss
- yamllint config ignores the plugin manifest yaml because it
follows DalamudPackager's 4-space indent convention rather than
yamllint's default 2
Changelogs are honest about the cross-plugin HITCH target from
v1.5.0 not landing this cycle.
Move the four leftover ASCII variants from the repo root into
HellionChat/images/ascii/ and add a README that explains which two
files are embedded in the plugin DLL versus which ones stay as study
material. The original paw file was split into a stipple version and
an outline version because the two paws were stacked in one source.
Attribution:
- fox-*.txt files are by Julia Moon, drawn for Hellion Chat, free to
use without attribution
- wolf-head-blazejkozlowski.txt is by Blazej Kozlowski, originally
published on asciiart.eu, kept as a style reference
Pulls the four-line fox-mini ASCII out of the embedded branding
resources and writes each line through IPluginLog before the existing
bootstrap line, so an /xllog reader sees the Hellion Forge mark on
every plugin load. The text provenance ("by Julia Moon - Hellion
Forge") follows the silhouette, then the version + fingerprint line
stays where it was.
Empty lines from the resource are skipped so the log stays compact.
Ship two ASCII variants as embedded resources under HellionChat.Branding:
- fox-banner.txt — full silhouette with "Hellion Forge" set inside the
body, rendered in the first-run wizard and the Settings Information
tab as a folded "about the makers" anchor
- fox-mini.txt — compact fox-head + curly-tail used by the DI-logger
bootstrap banner
A small HellionForgeAscii helper lazy-loads both strings; the wizard
and information-tab render them in a collapsed TreeNode using the
UiBuilder MonoFontHandle so the stipple-art lands pixel-aligned.
Both art files are self-made (Julia Moon, free to use) and travel with
the plugin DLL so a partial deploy can't lose them.
Two new self-test steps for the hybrid FontManager:
- FontManagerCtorSmokeStep proves all five handles land on the manager
after Phase-1 resolve (ItalicFont nullable per Config.ItalicEnabled)
and that no atlas-load exception is sitting on any of them
- FontPushSmokeStep proves IFontHandle.Push() returns without throwing
for the two main delegate handles right after plugin load
Both steps run on the framework thread via the xlperf self-test path
and are registered alongside the existing theme-switch step in
SelfTestRegistry.
Pull in the refreshed linter and tooling configs (editorconfig,
gitignore, gitattributes, prettierignore, prettierrc, markdownlint,
yamllint, env.example, dotnet-tools) and run prettier and markdownlint
in --fix / --write mode across the repo so the existing tree matches
the new rules.
- prettier 2-space indent on yaml/yml and json overrides, asterisk
strong, underscore emphasis, proseWrap always
- markdownlint MD007 indent aligned to 2 and MD049 to underscore so
prettier output stays passing
- preflight Block F also ignores CLAUDE.md (gitignored personal file)
- prettierignore extended to keep HellionChat.yaml manifest and the
NuGet packages.lock.json out of the formatter
No semantic content changed; csharpier, build, full build-suite
(729/729) and the new prettier/markdownlint/yamllint checks all green.
Drop the custom NewDelegateFontHandle that built our own FontAwesome
atlas slot and reuse Dalamud's UiBuilder.IconFontFixedWidthHandle
instead. One less delegate-build step in the ctor, and the handle is
host-managed so Dispose() leaves it alone.
The pre-cycle icon inventory verified that every site we push the
FontAwesome font for renders an icon that is present in the host's
fixed-width handle glyph range, so no rendering site changes.
Move font handle creation from BuildFonts() into the FontManager ctor
inside a single atlas.SuppressAutoRebuild() block. Axis, AxisItalic and
FontAwesome become init-only IFontHandle properties; RegularFont and
ItalicFont stay mutable so the live font-settings rebuild path keeps
working without a plugin reload.
- BuildFonts() renamed to RebuildDelegateFonts(), scope reduced to the
delegate fonts only
- BuildFontsAsync() removed; Task.Run had no purpose with ctor-init
- FontManagerInitHostedService deleted; PluginHostFactory drops the
matching AddHostedService registration
- PluginHostFactory FontManager registration takes IDalamudPluginInterface
via factory lambda
- Settings save path now calls RebuildDelegateFonts() instead of
BuildFonts()
- Plugin.Draw push site gets a null-forgiving for the nullable
RegularFont with a one-line WHY