Skip to content

Client Environment ​

Use this skill when a task needs client-side access to local platform or device environment features.

Human-facing docs: docs/systems/ClientEnvironmentSystem.md, docs/systems/ClientPlatformSystem.md, and docs/guides/ClientRuntimeCompatibility.md.

Fit Check ​

GoalUse
Open a QQ group join link or another allowed external linkClientEnvironment.TryOpenUrl(...)
Copy, paste, or clear short local textClientEnvironment.Clipboard
Read system language, project default language, or switch client localizationClientEnvironment.Localization with CultureInfo
Check whether the player is on Wi-FiClientEnvironment.Network
Read battery level, charging state, battery health, or remaining timeClientEnvironment.Power
Show FPS, ping, memory, draw calls, traffic, or jank count in a debug overlayClientEnvironment.Diagnostics.Current
Let a player submit client logs from a settings/help screenClientEnvironment.Diagnostics.TryUploadLogs() after Runtime.Supports(ClientRuntimeFeature.ClientLogUpload)
Display the exact client build version for diagnosticsClientEnvironment.Runtime.BinaryVersion
Display the current client capability contract version or explain the old-client boundaryClientEnvironment.Runtime.CapabilityVersion; see docs/guides/ClientRuntimeCompatibility.md
Check whether an optional client-binary feature is availableClientEnvironment.Runtime.Supports(...)
Query displays, select a desktop mode, inspect/restore the mobile Surface, control frame timing, scan QR codes, or query gamepadsClientPlatform after Runtime.Supports(ClientRuntimeFeature.NativeClientServices), minimum capability 9
Change per-game shadow/cluster/AO/skinning settings or capture and restore themGameRenderSettings after Runtime.Supports(ClientRuntimeFeature.GameRenderSettings), minimum capability 9
Store defaults or session-only options and observe option changesClientPlatform.GetOption/SetOption/Changed, minimum capability 9
Handle loss of the game Host connectionSubscribe to Game.OnServerDisconnected; the framework performs compatibility gating
Handle mobile app lifecycle changesSubscribe to Game.OnApplicationWillEnterBackground, OnApplicationDidEnterBackground, OnApplicationWillEnterForeground, OnApplicationDidEnterForeground, or OnApplicationLowMemory; the framework performs capability gating
Save graphics/audio preferencesClientSettings, not this skill
Store cross-device data, progress, currency, purchases, or inventoryCloudData or server-side systems, not this skill

Boundaries ​

  • Put usage under #if CLIENT.
  • New platform APIs require capability 9. Keep their provider and calls off the initialization path of existing APIs. The project checks capability before ordinary new API calls; initialization event subscription is gated by the framework.
  • Distinguish system display modes, default app Surface pixels, current render pixels, SDL window coordinates, GameUI viewport pixels and UI design coordinates. Default app Surface is not guaranteed to equal physical panel resolution. Mobile surface/mode requests are asynchronous; read actual values after resize.
  • Session-only options store data and notify this session; they do not invoke process-wide or legacy Lua handlers. Process/window/frame settings need explicit restoration when used temporarily.
  • Native diagnostics nullable fields mean unavailable or unsampled; do not replace them with zero or treat native Unit counts as managed Unit counts. Use GameDiagnostics.CaptureManaged() for current-end managed state.
  • Treat all values as local client state. They do not sync to the server and are not gameplay authority.
  • Do not use network type, battery level, FPS, or diagnostics to decide authoritative gameplay results.
  • Handle unavailable values. Battery percentage and remaining time are nullable; other platform values may return Unknown, false, or 0.
  • Do not expose internal implementation details in user-facing text or XML comments.
  • Use BinaryVersion only for diagnostics. Do not compare product build numbers to decide feature support.
  • CapabilityVersion == 0 means the client is old or unable to report the capability contract; treat optional features as unavailable.
  • Client updates are usually optional, so a newer dependency may run with a client whose capability version is lower. Keep capability gates until the target platform has explicitly completed a forced update that guarantees the required capability floor.
  • After a forced update is fully effective, features whose minimum capability version is at or below the confirmed floor may be treated as guaranteed in that platform/environment. Do not extend that guarantee to local, special-test, other-platform, or still-rolling-out clients.
  • Use Runtime.Supports(ClientRuntimeFeature...) for optional features, preferably during initialization rather than in a per-frame or per-message path.

URL Opening Pattern ​

csharp
#if CLIENT
using GameCore.ClientEnvironment;

bool opened = ClientEnvironment.TryOpenUrl("https://qm.qq.com/q/xxxx");
if (!opened)
{
    Game.Logger.LogWarning("未能打开外部链接");
}
#endif

Rules:

  • TryOpenUrl(string) requires an absolute URL or app link. Invalid strings return false.
  • The return value means the client accepted the request. It does not mean the browser, QQ, or target page finished loading.
  • The default URL allowlist is intentionally very limited.
  • QQ group join links should use the invitation link generated by QQ group sharing, commonly https://qm.qq.com/q/xxxx.
  • The xxxx part is a QQ-generated share code, not the group number. If a group number is 1021737313, first generate the actual group invitation link from QQ; do not synthesize https://qm.qq.com/q/1021737313.
  • Other websites or app links require project URL allowlist approval/configuration first.

Clipboard Pattern ​

csharp
#if CLIENT
using GameCore.ClientEnvironment;

ClientEnvironment.Clipboard.Text = "ABC-123";
var text = ClientEnvironment.Clipboard.Text;
ClientEnvironment.Clipboard.Clear();
#endif

Rules:

  • Use clipboard for short player-facing text such as invite codes or redemption codes.
  • Do not write secrets, payment state, account credentials, progression, inventory, or other sensitive gameplay data to the clipboard.

Localization Pattern ​

csharp
#if CLIENT
using GameCore.ClientEnvironment;
using System.Globalization;

var defaultCulture = ClientEnvironment.Localization.DefaultCulture;
var systemCulture = ClientEnvironment.Localization.SystemCulture;

ClientEnvironment.Localization.CurrentCulture = CultureInfo.GetCultureInfo("zh-CN");
string currentName = ClientEnvironment.Localization.CurrentCulture.Name;
#endif

Rules:

  • Use CultureInfo, not raw language strings, in the public-facing API pattern.
  • CultureInfo.Name is commonly zh-CN, en-US, etc. Do not assume lower-case output.
  • Compare culture names with StringComparison.OrdinalIgnoreCase or normalize before comparing.

Network, Power, And Diagnostics Pattern ​

csharp
#if CLIENT
using GameCore.ClientEnvironment;

bool isWifi = ClientEnvironment.Network.IsWifi;

var power = ClientEnvironment.Power.Current;
if (power.BatteryPercent is { } percent && percent < 20)
{
    Game.Logger.LogInformation("当前电量较低: {Percent}%", percent);
}

var diagnostics = ClientEnvironment.Diagnostics.Current;
Game.Logger.LogInformation(
    "FPS={Fps}, Ping={Ping}ms, DrawCalls={DrawCalls}, Jank={Jank}",
    diagnostics.FramesPerSecond,
    diagnostics.PingMilliseconds,
    diagnostics.DrawCalls,
    diagnostics.JankCount);
#endif

Rules:

  • Use diagnostics for debug overlays, issue reports, and performance hints.
  • Do not poll diagnostics or power every frame unless the UI truly needs live display.
  • Use nullable checks for battery percentage and remaining time.

Runtime Compatibility Pattern ​

For an in-game log upload button, use the client-only API after checking the installed client capability:

csharp
#if CLIENT
using GameCore.ClientEnvironment;

if (ClientEnvironment.Runtime.Supports(ClientRuntimeFeature.ClientLogUpload))
{
    bool accepted = ClientEnvironment.Diagnostics.TryUploadLogs();
}
#endif

The boolean reports request acceptance, not upload completion. Do not show a success confirmation immediately; this API has no completion callback. The existing client upload flow packages the local logs and associates them with the current project.

csharp
#if CLIENT
using GameCore.ClientEnvironment;
using GameCore.GameSystem;

long clientBuild = ClientEnvironment.Runtime.BinaryVersion;
bool supportsDisconnectEvent = ClientEnvironment.Runtime.Supports(
    ClientRuntimeFeature.HostDisconnectedEvent);

Game.OnServerDisconnected += () =>
{
    Game.Logger.LogWarning("与游戏服务端的连接已断开");
};
#endif

Rules:

  • BinaryVersion identifies the installed client build and may be unavailable in local/special builds. Use it for logs, issue reports, and display only.
  • CapabilityVersion is an advanced diagnostic value. Feature code should call Supports(...) instead of comparing capability numbers directly.
  • Game.OnServerDisconnected is already compatibility-gated by the framework. Project code may subscribe directly without performing its own version check.
  • Runtime capability values are immutable and cached by the framework. Still prefer initialization-time checks when selecting an optional implementation.

Validation ​

powershell
dotnet build src\GameEntry.csproj -c Client-Debug

Then run the client and verify:

  1. QQ group invitation links open; ordinary unapproved links fail or show the platform's blocked-link prompt.
  2. Clipboard operations work on the target platform.
  3. Localization changes update the intended client-side UI text.
  4. Network, battery, and diagnostics reads do not throw when values are unavailable.
  • docs/systems/ClientEnvironmentSystem.md
  • docs/ai/skills/client-settings/SKILL.md
  • docs/systems/ClientSettingsSystem.md
  • docs/ai/skills/ui-layout-api/SKILL.md