Appearance
Reading Live Game State
Use this page when you need real C4 accounts rather than placeholder addresses.
It covers read-only discovery on the public C4 PTR deployment: identifying the network, selecting the correct program, filtering account types, and decoding live fleets with @staratlas/dev-sage.
Deployment scope
These addresses and layouts describe the C4 public PTR deployment. Other SAGE deployments on the same z.ink chain use different program addresses and can use different account versions. Always make the target program address explicit.
Network and deployment identity
The C4 PTR runs on a dedicated Solana-compatible SVM chain, not Solana mainnet.
| Item | Value |
|---|---|
| HTTP RPC | https://testnet-rpc.z.ink |
| HTTP RPC alias | https://rpc1.z.ink |
| Genesis hash | 6qaAozzun2WV83PRDgqf79WXtbqfnezKB3demjxXV5EY |
| Read commitment | confirmed |
| Low-latency read commitment | processed |
Both HTTP endpoints returned the same genesis hash during the 2026-07-25 verification pass.
bash
curl -sS -X POST https://rpc1.z.ink \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"getGenesisHash","params":[]}'Checking the genesis hash prevents a healthy but incorrect RPC endpoint from being mistaken for C4.
C4 addresses
| Account or program | Address |
|---|---|
| SAGE program | C4SAgeKLgb3pTLWhVr6NRwWyYFuTR7ZeSXFrzoLwfMzF |
| Player Profile program | C4PRoFNroxxzdgeCoM31LJjYRg7kT6ymogSTAT99iD1u |
| Profile Faction program | C4FACQA1PpNRKrjQ2862ABNR42DTz7EzGj1uhTNFASwP |
| Game account | EKEj47SzaCjPM3m4T4vRXrrsVtkEmiNgPMMFye3AkXj4 |
| Faction Relation Tracker | E6X4yidKzV3NW559hrpJDwEmNiRpreXeYjzpZvG7J4n3 |
The game account is owned by the C4 SAGE program and is roughly 2.5 MB. It holds game-wide configuration and definition tables. Star systems and celestial bodies remain separate program-owned accounts; starbase data is nested in StarSystem.
Do not use public-mainnet SAGE program addresses against this RPC. getProgramAccounts only returns accounts owned by the program address you query.
Account discriminators
SAGE account data begins with an eight-byte discriminator. The generated package exports a get<Name>DiscriminatorBytes() helper for each account type.
| Account | Hex discriminator | Base58 filter |
|---|---|---|
Fleet | 6dcffb306a0288a3 | KNKT54ytpWW |
Game | 1b5aa67d4a647912 | 5aNQXizG8jB |
StarbasePlayer | c0ea905648130563 | ZGXhzS6juQW |
CelestialBody | b9251d7a0ed88e6d | Xy922tyvVh6 |
Character | 8c73a524f1996654 | QVZMSrEA6j5 |
StarSystem | cf207b0909fbdda9 | bePdXVNeenk |
RegionTracker | 171d1bdf479800e1 | 4sET8pW57x4 |
FactionConfig | 340cfea68f357c37 | 9hxYJKNnXp2 |
FactionOwnership | 50c73351876463e3 | EWeeHyLbPDL |
LoyaltyContribution | 80c61e873a4394a2 | NYGZ8NuB5e1 |
CargoDefinitionsCache | f8f77509cb1e1f92 | ieHiRVzMnM7 |
CurrencyConfigCache | e7cb8e6cd2ea07bf | fmhngiKjzmt |
The values above were checked against the generated 0.52.0 exports. Compute filter text in application code instead of hard-coding it:
ts
import { getBase58Decoder } from '@solana/kit';
import { getFleetDiscriminatorBytes } from '@staratlas/dev-sage';
const fleetDiscriminator = getBase58Decoder().decode(
getFleetDiscriminatorBytes()
);The Anchor-style derivation is sha256("account:<AccountName>")[0:8], but the generated helper should remain the application source of truth.
Fleet filter prefix
The beginning of the Fleet account has a fixed, useful discovery prefix:
| Offset | Size | Field |
|---|---|---|
0 | 8 | account discriminator |
8 | 1 | account version |
9 | 1 | PDA bump |
10 | 32 | gameId |
42 | 32 | ownerProfile |
74 | 32 | subProfile |
106 | 32 | subProfileInvalidator |
Use only offsets 0, 10, and 42 as durable memcmp filters. Fields after the identity prefix are decoded values, not a stable query API. The current 0.52.0 layout places faction, npcFactionId, and fleetLabel after this prefix, but future account versions can move later fields.
Do not filter fleets by dataSize. C4 fleet accounts are extensible and live account sizes vary.
Discover fleets for one game
This read returns account addresses only, keeping the RPC response small:
ts
const rpcUrl = 'https://rpc1.z.ink';
const sageProgram = 'C4SAgeKLgb3pTLWhVr6NRwWyYFuTR7ZeSXFrzoLwfMzF';
const game = 'EKEj47SzaCjPM3m4T4vRXrrsVtkEmiNgPMMFye3AkXj4';
const response = await fetch(rpcUrl, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
id: 'fleets',
method: 'getProgramAccounts',
params: [
sageProgram,
{
encoding: 'base64',
commitment: 'confirmed',
dataSlice: { offset: 0, length: 0 },
filters: [
{ memcmp: { offset: 0, bytes: 'KNKT54ytpWW' } },
{ memcmp: { offset: 10, bytes: game } }
]
}
]
})
});
const payload = await response.json();
if (payload.error) {
throw new Error(JSON.stringify(payload.error));
}
console.log(payload.result.map((account: { pubkey: string }) => account.pubkey));Stable NPC profiles
The Nemesis Engine continuously operates Jorvik and Relic Baron fleets on C4. Their profile addresses are stable enough to serve as live decoder fixtures:
| NPC faction | Owner profile | Minor faction id |
|---|---|---|
| Jorvik | B8PCrai4bqyoR5VAcjFnLixQXejVNReNw6JunbeEACLj | 4 |
| Relic Barons | DNJu1dC8sZFWDNtA2dhH2CPYaNzn556jRdqEmZbzWiq9 | 5 |
Filter ownerProfile at offset 42:
ts
const jorvikProfile = 'B8PCrai4bqyoR5VAcjFnLixQXejVNReNw6JunbeEACLj';
const filters = [
{ memcmp: { offset: 0, bytes: 'KNKT54ytpWW' } },
{ memcmp: { offset: 42, bytes: jorvikProfile } }
];NPC fleet churn
Bookmark the profiles, not individual fleet accounts. Nemesis closes and recreates fleets continuously. A verification snapshot on 2026-07-25 found 2,808 fleets for the C4 game, including 1,403 Jorvik and 379 Relic Baron fleets; these counts are evidence of the query working, not durable game constants.
The registry's minor faction ids are separate from Fleet.faction, whose generated enum remains Unaligned, MUD, ONI, and Ustur. The live NPC fleets sampled during verification decoded as faction: 0 and npcFactionId: 0; do not infer the Jorvik or Relic identity from either field alone. Use the known owner profile or the relevant faction-registry state.
Decode live fleet accounts
The discovery query above deliberately omits account data. Fetch a selected address, then use the generated decoder for the complete account:
ts
import { getFleetDecoder } from '@staratlas/dev-sage';
function decodeBase64(value: string): Uint8Array {
return Uint8Array.from(Buffer.from(value, 'base64'));
}
const fleetAddress = payload.result[0]?.pubkey;
if (!fleetAddress) {
throw new Error('No fleet accounts matched the filters');
}
const accountResponse = await fetch(rpcUrl, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
id: 'fleet',
method: 'getAccountInfo',
params: [fleetAddress, { encoding: 'base64', commitment: 'confirmed' }]
})
});
const accountPayload = await accountResponse.json();
const encoded = accountPayload.result?.value?.data?.[0];
if (!encoded) {
throw new Error(`Fleet not found: ${fleetAddress}`);
}
const decoder = getFleetDecoder();
const fleet = decoder.decode(decodeBase64(encoded));
const [x, y] = fleet.location.map((coordinate) => coordinate.toNumber());
console.log({
address: fleetAddress,
label: fleet.fleetLabel,
ownerProfile: fleet.ownerProfile,
location: [x, y],
state: fleet.state.__kind,
hp: fleet.hp,
sp: fleet.sp,
ap: fleet.ap
});This Node example uses Buffer. Browser code should decode base64 with a browser-safe helper.
Fixed-point values
Fleet coordinates use signed I8F56 fixed-point values. The generated client returns a FixedPoint object with raw, fractionalBits, and .toNumber().
Call .toNumber() before arithmetic. String coercion and JSON output can make a fixed-point object look like a plain number even though it is not one.
State payloads
Fleet.state is a generated discriminated union:
ts
const kind = fleet.state.__kind;
const state = fleet.state.fields[0];Important movement shapes:
MoveWarp:state.to,state.warpStart, andstate.warpFinish; the top-levelfleet.locationis the departure point.MoveSubwarp:state.journey.from,state.journey.to,state.journey.departureTime, andstate.journey.duration.
Timestamp interpolation can produce a useful display position, but it is not authoritative for subwarp. The game can settle progress incrementally and constrain it by remaining fuel.
Derive a fleet PDA
Fleet PDA seeds are:
- the UTF-8 bytes for
Fleet - the game address
- the owner-profile address
- the first 32 bytes of the zero-padded 64-byte fleet label
- the second 32 bytes of that label
The generated helper accepts the four domain seeds after the fixed Fleet prefix:
ts
import { address } from '@solana/kit';
import { findFleetPda } from '@staratlas/dev-sage';
function splitFleetLabel(label: string): [number[], number[]] {
const encoded = new TextEncoder().encode(label);
if (encoded.length > 64) {
throw new Error('Fleet labels are at most 64 UTF-8 bytes');
}
const padded = new Uint8Array(64);
padded.set(encoded);
return [Array.from(padded.slice(0, 32)), Array.from(padded.slice(32))];
}
const [fleetLabelPart1, fleetLabelPart2] = splitFleetLabel('JOR:example');
const [fleetAddress, bump] = await findFleetPda({
gameId: address('EKEj47SzaCjPM3m4T4vRXrrsVtkEmiNgPMMFye3AkXj4'),
ownerProfile: address('B8PCrai4bqyoR5VAcjFnLixQXejVNReNw6JunbeEACLj'),
fleetLabelPart1,
fleetLabelPart2
});
console.log({ fleetAddress, bump });Because Nemesis labels and accounts churn, derive a PDA when you know the current label; otherwise discover current fleet accounts from the stable profile.
Verification boundaries
During the 2026-07-25 documentation pass:
- npm still reported
@staratlas/dev-sage@0.52.0as latest - the discriminator table matched the generated package exports
- both documented RPC hostnames returned the same z.ink genesis hash
- the C4 game account was owned by the documented C4 SAGE program
- the game and owner-profile filters returned live fleet accounts
- a current Jorvik fleet decoded successfully with
getFleetDecoder()
Counts, fleet addresses, labels, positions, states, RPC software versions, and account sizes remain point-in-time observations. Re-query them when your application needs current state.
