Privacy Policy — DRAFT
THIS IS A DRAFT FOR LEGAL REVIEW. IT IS NOT LEGAL ADVICE AND IT IS NOT IN FORCE.
It was written by reading the game's own source code and database schema, so that a lawyer does not have to start from a blank page. Everything below describes what the software actually does today, including the parts that are not flattering. It has not been reviewed by anyone qualified to review it.
Do not rely on it until a lawyer has been through it. Every
[STEFANO: ...]marker is a fact only Stefano can supply.Note the tension, deliberately left visible: this document is already rendered publicly at /privacy and is linked from the registration form (see gap 6). Every
[STEFANO: ...]marker below is therefore visible to visitors. Clear them before the alpha widens.A short list of gaps — things a privacy policy would normally promise that this service cannot currently deliver — is at the bottom under "Known gaps".
Reviewing lawyer: the one item most likely to need your attention is the minimum age. It is boxed in section 10.
Draft date: 2026-08-07 Answers folded in: 2026-08-07 (controller, contact email, minimum age 13) Answers folded in: 2026-08-07 (legal name confirmed, establishment: Malta, supervisory authority, Art. 27 representative resolved as not required)
1. Who is responsible for your data
The data controller is Stefano Mastini, an individual trading under his own name as Fantasy Kingdoms, established in Malta. There is no company — the controller is a natural person.
[STEFANO: postal address of the controller. As a sole trader this is your own address and Art. 13 GDPR expects it to be published. Ask the lawyer whether a service address is acceptable in Malta.]
You can contact us about anything in this policy — including any of the requests in section 9 — at support@playfantasykingdoms.com.
Art. 27 EU representative — not required. Do not re-add this. Art. 27 GDPR applies only to controllers established outside the Union. The controller is established in Malta, an EU member state, so the obligation does not arise and no EU representative needs to be appointed or named here. This is recorded explicitly because an earlier draft carried it as an open question; it is now closed.
2. What this covers
- The website and account portal (playfantasykingdoms.com).
- The game client and the game servers.
- The community Discord server, to the extent described in section 6.
3. What we collect, and why
When you create an account
| Data | Why | Where it is stored |
|---|---|---|
| Username | Identifies you in the game and to other players | master.account |
| Email address | Identifies your account and lets us contact you about it | master.account |
| Password | Signing you in | master.account, hashed — see section 4 |
| Invite code you used | Registration is invite-only; lets us trace who invited whom | fk_web.invite_code, fk_web.invite_redemption |
| Your IP address | Abuse prevention and rate limiting | fk_web.invite_redemption, fk_web.login_attempt, fk_web.audit_log |
| Account creation time | Account administration | master.account |
| Your date of birth | Checking you meet the minimum age of 13 — see section 10 | master.account.birthdate |
| That you accepted these documents, when, which version, your IP and browser | Evidence of what you agreed to | fk_web.terms_acceptance |
Your username is visible to other players. Your email address and your date of birth are not, and are never shown in the game or to other players.
On the date of birth specifically: we ask for it once, at registration, to check you are old enough (section 10). We do not use it for anything else — no birthday messages, no age-based content, no profiling. We keep the date rather than only a yes/no answer for the reason given in section 10.
When you sign in
| Data | Why |
|---|---|
| Your IP address | Rate limiting, so nobody can brute-force passwords |
| Your browser's user-agent string | Security auditing on the website |
| Whether the attempt succeeded or failed, and when | Rate limiting and security auditing |
| The username or email you typed | Rate limiting — including on failed attempts, where the value typed may not be yours |
The game server also records the time you last logged in and last logged out.
When you play
| Data | Why |
|---|---|
| Your characters — name, appearance, class, race, level, stats, inventory, position, quest progress, skills, currency | It is the game. Without it there is nothing to log back into |
| Your IP address, in server and web-server connection logs | Operating the service, diagnosing faults, blocking abuse |
| Gameplay events the server records against your account (for example item and combat events) | Debugging, and investigating cheating or exploits |
When you change your password or email
We record that the change happened, when, from which IP address, and with which browser. We do not keep the old value.
Cookies
The website sets one cookie, fk_session. It keeps you signed in. It is a signed token containing your account ID and username, it is httpOnly, and it expires after [STEFANO: confirm the value of FK_SESSION_TTL_HOURS in production — the code default is 168 hours, i.e. one week].
That is the only cookie. There is no advertising cookie, no tracking pixel, and no analytics service of any kind on the website — we checked the code, not just our memory of it.
4. How your password is stored — read this one
Your password is not stored in plain text. It is stored as a salted MD5 hash: a random 32-character salt is generated for your account, and we store md5(password + salt) alongside the salt.
We are telling you this plainly because salted MD5 is weak by modern standards. MD5 is fast, and being fast is exactly the wrong property for a password hash — it makes guessing cheaper than a modern algorithm like bcrypt or Argon2 would.
We did not choose it freely. The game runs on the Atavism MMO engine, whose authentication server defines this format, and an account has to work in both the website and the game client. Changing it means changing the game engine's authentication.
What we do to compensate:
- The salt is unique per account and generated with a cryptographically secure random number generator, so one leak does not unlock every account at once.
- Failed sign-in attempts are rate limited per account and per IP address, which makes online guessing slow.
- Password comparisons are constant-time, so timing does not leak information.
- The hash is never sent to your browser or the game client.
- The website is served over HTTPS in production, so your password is encrypted in transit.
What this means for you: do not reuse a password here that you use anywhere else. That advice is good everywhere, and it matters more here than usual.
[STEFANO: this section is honest, which is the right call, but it is also an open statement that the auth stack is below current norms. Worth asking the lawyer how it should be worded, and worth putting "replace salted MD5" on the roadmap so the next version of this policy can say something better.]
5. Our lawful basis for using your data (UK/EU GDPR)
The controller is established in Malta, an EU member state, and the servers are in Germany. EU data protection law therefore applies directly, to all players, wherever they live.
| What | Lawful basis |
|---|---|
| Account, characters, sign-in — everything needed to give you the game | Performance of a contract (Art. 6(1)(b)) |
| Date of birth, to check the minimum age | Legal obligation / compliance (Art. 6(1)(c) read with Art. 8) — we are not permitted to offer this service to a child below the threshold without parental consent, so we have to know. [STEFANO: the lawyer may prefer legitimate interests (Art. 6(1)(f)) here; Art. 6(1)(c) is arguable because Art. 8 constrains rather than mandates. Either way the processing is minimal and one-off.] |
| Record of your acceptance of these documents | Legitimate interests (Art. 6(1)(f)) — being able to show what you agreed to and when |
| IP logging, rate limiting, audit logs, anti-cheat, abuse investigation | Legitimate interests (Art. 6(1)(f)) — keeping the service running and other players safe |
| Crash reports (section 6) | [STEFANO: legitimate interests is the likely answer, but crash reporting is currently on by default with no opt-out and no notice at install time. Ask the lawyer whether that survives a balancing test or whether it needs consent.] |
| Discord onboarding answers (section 6) — and the age answers collected there before 2026-08-07, which are still in the staff channel | [STEFANO: this is data you volunteered to a Discord bot. Consent is the likely basis, but nothing today records that consent. Flag it.] |
6. Who else receives your data
We do not sell your data. We do not share it for advertising. There is no ad network, no analytics provider, and no data broker involved.
Data does reach these third parties:
Hetzner (Germany) — hosting
The game server, the database and the website all run on a dedicated server rented from Hetzner Online GmbH, located in Germany. Hetzner hosts the machine; they do not use your data for their own purposes. [STEFANO: confirm the data centre location — Falkenstein or Nuremberg — and confirm whether you have a signed data processing agreement (AVV) with Hetzner. If not, get one; it is a standard download in the Hetzner console.]
Unity — crash reporting
The game client has Unity Cloud Diagnostics crash reporting enabled. When the game crashes or throws an error, a report is sent automatically to Unity's servers.
A crash report can contain:
- the crash itself — the error and the stack trace,
- the last few lines of the game's log output before the crash,
- information about your computer, such as operating system, hardware and graphics device,
- an identifier for your installation.
This happens automatically, and there is currently no way to turn it off from inside the game, and no prompt before the first report is sent. Unity acts as a processor for us and its own privacy policy also applies to that data.
[STEFANO: this is the flow most likely to attract a question. It was enabled recently, is on by default, is silent, and reaches a US-headquartered company. Two things the lawyer will want to know: (a) is there a data processing agreement in place with Unity for this project, and (b) what is the transfer mechanism for any data reaching the US. It would also be worth adding an opt-out toggle in the client's settings — it makes this section much easier to defend.]
[STEFANO: I could not verify from the codebase exactly which fields Unity's crash reporter transmits. The list above is drawn from the settings in the project (log buffer of 10 lines, editor exception capture on) and from what the service does generally. Confirm the exact contents against Unity's current documentation before publishing, and do not publish a field list you have not confirmed.]
Discord — community and onboarding
Our community lives on Discord. Anything you post there is handled by Discord under Discord's own privacy policy, and we do not control it.
Our onboarding bot additionally asks you, when you join:
- which class you like, and which region you are in,
- how you heard about us.
Your answers are posted by the bot into a private staff channel on our Discord server, where they stay indefinitely.
The bot does not ask your age. It used to, and the answer was posted into that staff channel; the question was removed on 2026-08-07 and no new answers are collected. [STEFANO: answers collected before that date are still sitting in the staff channel's message history. Deleting those messages is a manual job in Discord and nobody has done it. Do it, or ask the lawyer whether it can stay.]
Answering is entirely voluntary and nothing in the game depends on it.
Nobody else
No payment processor (nothing costs money), no email provider (the site cannot send email yet), no analytics, no advertising.
7. Transfers outside the EU
Data lives in Germany. The exceptions are the third parties in section 6: crash reports go to Unity, and anything on Discord is held by Discord — both are US companies who may process data outside the EU. [STEFANO: the lawyer needs to name the transfer mechanism relied on for each — usually Standard Contractual Clauses or the EU-US Data Privacy Framework. Do not guess at this; it is a specific factual question about each provider's current terms.]
8. How long we keep things
Honestly stated, because most of this is a consequence of how the software was built rather than a considered policy:
| Data | Kept for |
|---|---|
| Account (username, email, password hash) | Until you ask us to delete it. There is no automatic expiry |
| Date of birth | For as long as the account exists — it lives on the account row and is erased with it. Not on a separate timer |
| Characters and game progress | Until you ask us to delete it, or until the pre-launch wipe — all characters will be deleted before the game launches |
| Failed and successful sign-in attempts (username typed + IP) | 7 days, then deleted automatically |
| Website audit log (account ID, action, IP, browser) | 180 days, then deleted automatically |
| Invite redemptions (which code produced which account) | Until you ask us to delete your account. The IP address attached to the redemption is erased after 90 days, and the rest of the row is kept so we can tell which invite created which account |
| Password reset tokens (account, IP) | Deleted 7 days after the token expires |
| Your acceptance of these terms (account, time, policy version, IP, browser) | For as long as the account exists. This is the record of what you agreed to and when, so it is deliberately not on a timer — it is erased with the account |
| Game and web server log files | [STEFANO: unknown. Check whether logrotate is configured on the Hetzner box for nginx and for the Atavism server logs, and put the real number here.] |
| Database backups | 7 days on the server; 30 days off-site, where off-site backups are configured |
| Crash reports | Held by Unity under Unity's retention rules, not ours |
| Discord messages and onboarding answers | Indefinitely, on Discord |
The four automatic windows above are enforced by a sweeper that runs inside the website process once a day. The numbers are configuration, not prose: they live in retentionConfig in website/src/lib/config.ts (overridable per deployment via FK_RETAIN_* environment variables) and are applied by website/src/lib/data/retention.ts. If those numbers are changed, this table is wrong — treat a change to one as a change to both.
Everything not in that list is still deleted by hand. Note also that a deletion request cannot reach data already inside a backup; those copies age out on the backup schedule above instead.
[STEFANO: the retention windows above are engineering defaults chosen to be defensible, not legal advice. 7 days for sign-in attempts, 180 days for the audit log, 90 days before an invite redemption's IP is erased. Ask the lawyer whether any of them should be shorter — shortening them is a one-line environment-variable change, no code and no migration.]
9. Your rights
If you are in the UK or EU, you have the right to:
- get a copy of the personal data we hold about you,
- correct anything wrong,
- have it deleted,
- object to or restrict how we use it,
- take it elsewhere in a portable form,
- complain to a data protection authority.
Some of these you can do yourself; most you cannot, and we would rather say so than imply a button exists.
What you can do yourself, on the website:
- see your account details and your characters,
- change your email address,
- change your password.
What you have to email us for:
- a copy of your data,
- correcting your username,
- deleting your account and your characters,
- anything else in the list above.
Email support@playfantasykingdoms.com from the address on your account. A human will action this by hand — there is no automated deletion tool. We will aim to respond within 30 days, which is what the law requires.
[STEFANO (2026-08-07): "a human does it by hand" is still accurate, but the human now has instructions: docs/legal/DATA_DELETION_RUNBOOK.md lists every table holding personal data across all five schemas, the order to delete in, and how to produce an export. There is still no self-service button for a player.]
[STEFANO: no data export or deletion mechanism exists in the code at all. The honest wording above ("a human does it by hand") is fine and lawful for a small alpha, but you must actually be able to do it — including deleting the character rows in the game database, not just the account row. Worth writing that runbook before a request arrives, rather than after.]
You have the right to lodge a complaint with a supervisory authority. Our lead supervisory authority is the one for our place of establishment:
The Information and Data Protection Commissioner (IDPC), Malta. [STEFANO: verify current IDPC contact details before publication]
You may also complain to the supervisory authority in the member state where you live or work, or where you think the problem happened — you are not restricted to ours. In the UK that is the ICO.
10. Children
You must be at least 13 years old to create an account or play.
Registration asks for your date of birth. If the date you give puts you under 13, the account is not created — nothing is stored, and you are told plainly why rather than shown an error.
If you are 13 or over, we store the date of birth itself on your account (master.account.birthdate). We keep the date rather than only a "yes, over 13" flag for one reason: the age threshold that actually applies to you depends on which country you are in, and it is not the same in every one (see the note below). A stored yes/no answer to "are you over 13?" cannot answer "are you over 16?", so keeping the date is what lets us apply the right threshold to the right players without having to ask every existing player again. It is used for nothing else, and it is deleted when your account is deleted.
We do not knowingly collect data from anyone under 13. If you believe a child under 13 has an account, contact us at support@playfantasykingdoms.com and we will close it and delete the data on it.
For the reviewing lawyer — the chosen age threshold
This is a deliberate decision by the controller, recorded here so it is not mistaken for an oversight.
- The threshold chosen is 13.
- Malta — where the controller is established — has itself set its Art. 8 digital-consent age at 13. It did so by the Processing of Children's Personal Data in relation to the Offer of Information Society Services Regulations, Subsidiary Legislation 586.11 under the Data Protection Act 2018 (Cap. 586). The chosen minimum is therefore consistent with the controller's own jurisdiction, not in tension with it.
- GDPR Art. 8(1) sets the default at 16 but expressly permits member states to set anything down to no lower than 13. Malta took the lower bound.
- Member states have not converged. Several set 16 — including Germany, where this service's servers and database are hosted. Others set 13, 14 or 15.
- The applicable threshold generally follows where the child is, not where the controller is established and not where the server sits. So Malta's 13 does not automatically travel with the service: a player aged 13, 14 or 15 habitually resident in a member state that sets 16 can register under this policy, but their personal data may not lawfully be processed on the basis of their own consent — a parent or guardian's authorisation would be required. This service has no mechanism to obtain or verify parental consent.
- Which member state's threshold applies to a given player is itself a question the codebase cannot settle.
What is needed: confirmation of the threshold for each market Stefano intends to operate in, and a decision on what happens to 13–15 year olds in the 16 states — raise the global minimum, geo-vary it, or build a parental consent flow.
Note also that Germany's role here is as the processing location only. The controller's establishment — which is what sets the lead supervisory authority under section 9 — is Malta.
Related: US COPPA independently prohibits knowingly collecting personal data from under-13s without verified parental consent. A minimum of 13 is consistent with COPPA; a minimum below 13 would not have been.
Related: the date-of-birth field is self-declared and unverified. It is a gate, not proof of age, and this policy does not claim otherwise.
11. Security
- The website is served over HTTPS in production.
- The database is not reachable from the internet — it listens on localhost only and the firewall does not open its port.
- Passwords are hashed and salted, with the important caveat in section 4.
- Sign-in attempts are rate limited per account and per IP.
- The session cookie is httpOnly, same-site, and signed.
- All database queries are parameterised.
- Every state-changing action on the website is logged.
No system is perfectly secure, and this one is run by a very small team. If you find a security problem, please email support@playfantasykingdoms.com rather than posting it publicly, and we will fix it.
12. Changes to this policy
If we change how we use your data we will update this page and say so on the website and in the community channels.
13. Contact
Stefano Mastini, trading as Fantasy Kingdoms, established in Malta.
Email: support@playfantasykingdoms.com — privacy questions, data subject requests (section 9), and security reports.
[STEFANO: postal address — same open question as section 1.]
Known gaps — for Stefano and the lawyer, not for publication
These are things the service does not currently do that a privacy policy would normally assume. They are stated here so nobody has to discover them later.
- ~~There is no age gate.~~ PARTLY CLOSED 2026-08-07. Website registration now has a required date-of-birth field. It is validated in
website/src/lib/validation.ts(dateOfBirthSchema, folded intoregisterSchema) and re-checked server-side inwebsite/src/lib/auth/actions.tsbefore anything is written, so removing the client-side input does not get past it. Accepted dates are stored inmaster.account.birthdate— the column was already there and unused by the Atavism engine (nothing in the Java or Groovy source reads or writes it), so there is no schema change and no migration.
A date of birth was chosen over a "confirm you are 13+" checkbox: a checkbox is one reflexive click and records nothing meaningful, whereas a date is a deliberate statement and remains re-checkable against a different threshold.
On the threshold itself. 13 is the threshold Malta — the controller's place of establishment — has itself set under GDPR Art. 8 (S.L. 586.11 under the Data Protection Act 2018), so the choice is aligned with the controller's own jurisdiction. What remains open is that Art. 8 lets member states set anything from 13 to 16, several set 16 — including Germany, where the servers are — and the applicable threshold generally follows where the child is, not where the controller or the server is. The full note is boxed in section 10 above and mirrored in section 6 of the EULA. This is a decision the controller has made and a lawyer needs to confirm per market; it is not an implementation gap.
Also still open:
- The date of birth is self-declared and unverified. It is a gate, not proof of age.
- The launcher and game client have no age check at all, so an account created directly by an admin bypasses it entirely — the same hole the terms acceptance has (gap 6).
- Accounts created before 2026-08-07 have no birthdate and were not backfilled, for the same reason no consent rows were backfilled: nobody asked those players, so inventing an answer would be worse than having none.
SELECT id, username FROM master.account WHERE birthdate IS NULLlists them. - There is no parental consent mechanism of any kind, so if the threshold has to rise to 16 anywhere, the only available response today is to refuse the registration.
- ~~The Discord bot asks every new member their age, and stores the answer.~~ CLOSED 2026-08-07. The age field was removed from the bot's onboarding modal and from the profile message posted to the staff channel (
discord-bot/src/onboarding.js). Nothing collects an age anywhere in the product now. Still open: answers collected before that date remain in the staff channel's Discord message history and have to be deleted by hand.
- ~~No deletion or export mechanism exists.~~ PARTLY CLOSED 2026-08-07.
docs/legal/DATA_DELETION_RUNBOOK.mdnow documents exactly which tables hold personal data across all five schemas, the order to delete in, and how to produce an export. It is a runbook plus a dry-run-by-default script, not a self-service button in the account area — an erasure request is still a job someone does deliberately, which for a request that cannot be undone is arguably the right shape. Still open: no player-facing "download my data" or "delete my account" control exists.
- ~~Retention is effectively "forever" for three tables.~~ CLOSED 2026-08-07.
login_attempt,audit_log,invite_redemption.ipandpassword_reset_tokenare now swept on a schedule bywebsite/src/lib/data/retention.ts, started fromwebsite/src/instrumentation.tswhen the site boots. The windows are in section 8 above.pruneOldAttempts()— which was defined but called from nowhere — is now a thin delegate to that sweeper.
- Crash reporting is on by default, silent, and cannot be turned off by the player. Reports go to Unity and may include system information and log output. There is no notice at install time and no in-game toggle.
- ~~Nothing captures agreement to any terms.~~ PARTLY CLOSED 2026-08-07. The website registration form now has a required acceptance checkbox linking to
/termsand/privacy(which render these two drafts, banners intact), and the acceptance is recorded infk_web.terms_acceptancewith a timestamp, the policy version string, the IP and the user agent — inside the same transaction as the account insert, so an account cannot exist without one. Still open, and important:
- Accounts created before that date have no acceptance record, and none was backfilled. Manufacturing consent rows for people who were never shown anything would be worse than having none. They need a re-acceptance prompt if you want coverage.
- The launcher and game client still have no click-through of any kind. Someone who was given an account by an admin, rather than registering on the website, has still never been shown these documents.
- The recorded version string is
2026-08-07-draft. It honestly records that what was accepted was a draft pending legal review. When the documents are finalised the version must be bumped and every existing account re-prompted; do not retro-stamp the old rows.
- Log file retention on the server is unverified. I could not confirm from the repository whether logrotate is configured for nginx or for the Atavism server logs, so the retention table above has a placeholder rather than a number. Server logs will contain player IP addresses.
- Password reset does not exist. The
password_reset_tokentable is present but no code uses it, and the site cannot send email. Account recovery is a manual reset by an admin, which means an admin can set any player's password — worth being aware of, and worth a line in an internal access policy eventually.