Titles report a progressive trophy's progress with _UpdateTrophyProgress
events. Their udsNN.ucp rules feed $._trophy_progress into an int32
statistic with the max aggregation, filtered on $._trophy_id, and the
trophy's unlockCondition names that statistic. Those statistics and
rules were skipped and the events were dropped, so such trophies never
reported progress or unlocked (21 of Borderlands 4's 64).
- Read max statistics (largest value posted, from initialValue) and
int32 statistics (0 to 2147483647).
- Read a rule condition with one == property equal to an unsigned
integer, and apply the rule only to events whose property matches.
- Post _UpdateTrophyProgress through the statistic rules.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Implement sceNpTrophy2GetRewardIcon matching PPSA06888 and SharpProspero signatures. Zeroes size if non-null and returns SCE_NP_TROPHY2_ERROR_ICON_FILE_NOT_FOUND following KytyPS5 when icons are unavailable offline. Updates guest tests and technical debt documentation.
sceNpTrophy2GetTrophyInfo threw std::invalid_argument for a trophy id
that is not in the title's trophyNN.ucp, so a title that asks for one
stopped (PPSA14363 asks for trophy 19 of ids 0 to 18). KytyPS5 returns
NP_TROPHY2_ERROR_INVALID_TROPHY_ID (0x8055390A) there without writing
either output; return the same. A group that is not in the data still
throws.
Co-Authored-By: Claude <[email protected]>
USAGE.md describes the refused posted unlocks and their log line, and
says that the icon queries and a damaged or unwritable unlock or
statistics file stop the game with an error. The debt entries of
libSceNpTrophy2 and libSceNpUniversalDataSystem no longer say that any
posted id is stored. Both USAGE.md and the libSceNpTrophy2 entry say
that without trophy data the platinum is not unlocked when the other
trophies are.
Events other than the trophy events go through the title's
sce_sys/uds/udsNN.ucp: stats_definition.json gives the statistics that
keep their latest uint32 or uint64 value, and stats_extraction.json the
event property ($.name, dotted for nested objects) that sets each one.
Statistics are kept per user and service label in
_trophy/<user id>_<service label>.stats.txt next to the unlocks.
After each event that sets a statistic, every trophy's unlockCondition
(ge, gt, le or lt against its targetValue) is checked on the values that
event set and the stored values of the other statistics, so a later
event setting the same statistic cannot hide what this one reached.
Reached trophies unlock through the same path as _UnlockTrophy.
Progressive trophies report their statistic as progress, and
APS5_TRACE_UDS prints each statistic an event sets. A missing udsNN.ucp
is ignored and a malformed one is logged once.
Events and property objects keep their typed properties instead of
dropping them. PostEvent records an _UnlockTrophy event with an int32
_trophy_id for the user and service label of its context, logs why a
trophy event is ignored, and prints every posted event with
APS5_TRACE_UDS.
Unlocks are kept in _trophy/<user id>_<service label>.txt in the title's
run directory, one "<trophy id> <RTC tick>" line per trophy, and each new
unlock logs "[trophy] <title id> unlocked trophy <id> for user <id>".
GetTrophyInfo, GetTrophyInfoArray, GetGroupInfo, GetGroupInfoArray and
GetGameInfo report the recorded unlocks, their times, the unlocked
counts by grade and the progress percentage. An unlock also unlocks the
platinum the trophy names once every trophy naming that platinum is
unlocked. Unlock listeners let a later notification subscribe. Removing a
listener waits for its running callback, and a listener removed during a
dispatch is not called by it.
The title's sce_sys/trophy2/trophyNN.ucp is read on first use with a
bounds-checked .ucp reader and a small JSON reader. tropconf.json gives
each trophy's grade, group, hidden and reward flags, platinum and
unlock condition, and tropmeta_<tag>.json its texts in the console
language, then en-US, then the file's default language. Contexts
remember their user and service label, so the queries find the file.
The trophy, group and game queries report these names, grades, groups
and counts, list every trophy and group, and give progressive trophies
their target. Unlocks are not recorded yet, so every trophy reports as
locked and nothing is written to the host. A missing file is ignored
and a malformed one is logged once; either way the queries keep their
placeholders. A JSON document that nests deeper than 64 levels or holds
more than 1048576 values counts as malformed, which keeps the parsed
values to about 140 MiB.
A progress value is {int32 type, 4 reserved bytes, uint64 value}, as
KytyPS5 lays it out, which makes the trophy details 1312 bytes and the
trophy data 32 bytes. Size checks for every query structure keep the
layout fixed, and the technical debt names its single public source.
The bundled PSNCore.prx of PPSA06888 calls sceNpTrophy2GetRewardIcon
with (context, handle, reward_id, buffer, size): once with a null buffer
to read the size, then with a buffer of that size. The function still
throws because the reward icon data is unknown.
Co-Authored-By: Claude Haiku 5.5 <[email protected]>