A new post attachment type beside polls: the composer gains a Start
Opinion War builder (same disabled-inputs opt-in as the poll builder)
that names exactly two factions; the battle runs for exactly 7 days
from post creation. Members join a side, may defect at any time
(damage already dealt stays with the faction it was dealt to), and
fight once per 24 hours per battle. A fight spends 25 Code Farm coins
and deals deterministic level-weighted damage: 100 + 10 * min(level,
20) HP, so a newcomer deals 110 and a veteran caps at 300 - no
randomness anywhere.
The battle renders on the post card as a CSS pixel-art battlefield
(box-shadow sprites: castles, faction flags, marching soldiers, a
flickering campfire; steps() animation, disabled under reduced motion)
with live HP bars, a countdown, the viewer's faction strip, top
contributors and an event ticker. Live frames ride pub/sub on
public.battle.{uid} via a relay on the service-lock owner, with the
durable opinion_war_events trail (per-war atomic seq) as the source of
truth and a 15s incremental poller as fallback. /battles lists battles
with active/ended/mine filters, search and pagination.
Every mutation is a conditional UPDATE via conditional_update_row: the
fight sequence claims the cooldown first, then spends coins, then lands
the damage, compensating earlier steps on any later refusal so a crash
costs a turn, never coins. Resolution is lazy on read (no cron):
an exactly-once CAS computes the winner in the statement, awards XP
(participation, winner bonus, top damage dealer bonus; draws pay
participation only), emits the result event and notifies fighters. The
OpinionWarService backstop resolves unviewed wars and sends
fight-ready notifications, exactly-once via a marker CAS.
Fan-out: battle notification type, four badges, audit keys
(battle.create/join/switch/fight/resolve), Devii actions (join/fight
confirm-gated), API docs group, docs prose page, sitemap and topnav
entries, REPORTABLE_TARGETS registration, post-delete cascades,
README and nested CLAUDE.md documentation.
Verified with the four-layer procedure: property checks over the full
damage domain, 1200-step stateful fuzz (hp-sum invariant, coins never
negative, resolved totals frozen), and real 8-process races proving
exactly-once semantics for concurrent fights, double-spends across two
wars, resolution XP and double-joins. Persisted tests in
tests/unit/services/opinionwar, tests/api/battles, tests/e2e/battles
and tests/api/posts/create.py.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
213 lines
4.1 KiB
Python
213 lines
4.1 KiB
Python
# retoor <retoor@molodetz.nl>
|
|
|
|
from __future__ import annotations
|
|
|
|
from devplacepy.schemas.base import (
|
|
ActionResult,
|
|
ErrorOut,
|
|
ValidationErrorOut,
|
|
_Out,
|
|
)
|
|
from devplacepy.schemas.content import (
|
|
AttachmentOut,
|
|
BadgeOut,
|
|
CommentEditOut,
|
|
CommentItemOut,
|
|
CommentOut,
|
|
GistOut,
|
|
MessageOut,
|
|
NewsOut,
|
|
NotificationOut,
|
|
PollOptionOut,
|
|
PollOut,
|
|
PostOut,
|
|
ProjectFileOut,
|
|
ProjectFileRawOut,
|
|
ProjectFilesOut,
|
|
ProjectOut,
|
|
ReactionsOut,
|
|
UserOut,
|
|
VotesOut,
|
|
)
|
|
from devplacepy.schemas.listings import (
|
|
ConversationOut,
|
|
FeedItemOut,
|
|
FeedOut,
|
|
GistDetailOut,
|
|
GistItemOut,
|
|
GistsOut,
|
|
LeaderboardEntryOut,
|
|
LeaderboardOut,
|
|
MessageItemOut,
|
|
MessagesOut,
|
|
NewsDetailOut,
|
|
NewsListItemOut,
|
|
NewsListOut,
|
|
NotificationGroupOut,
|
|
NotificationItemOut,
|
|
NotificationsOut,
|
|
PostDetailOut,
|
|
ProjectDetailOut,
|
|
ProjectListItemOut,
|
|
ProjectsOut,
|
|
SavedItemOut,
|
|
SavedOut,
|
|
)
|
|
from devplacepy.schemas.profile import (
|
|
MediaItemOut,
|
|
ProfileOut,
|
|
TelegramPairOut,
|
|
)
|
|
from devplacepy.schemas.issues import (
|
|
IssueAttachmentsOut,
|
|
IssueCommentOut,
|
|
IssueDetailOut,
|
|
IssueItemOut,
|
|
IssueJobOut,
|
|
IssuesOut,
|
|
)
|
|
from devplacepy.schemas.containers import (
|
|
AdminBotsOut,
|
|
AdminContainerEditOut,
|
|
AdminContainerInstanceOut,
|
|
AdminContainersOut,
|
|
AdminWorkspacesOut,
|
|
BotFrameOut,
|
|
ContainersOut,
|
|
EditorProfileOut,
|
|
InstanceOut,
|
|
ScheduleOut,
|
|
TunnelOut,
|
|
WorkspaceFlagOut,
|
|
WorkspaceOut,
|
|
WorkspaceViewOut,
|
|
)
|
|
from devplacepy.schemas.jobs import (
|
|
DbQueryJobOut,
|
|
DeepsearchJobOut,
|
|
DeepsearchSessionOut,
|
|
ForkJobOut,
|
|
PlanningJobOut,
|
|
SeoJobOut,
|
|
SeoMetaOut,
|
|
SeoReportOut,
|
|
ZipJobOut,
|
|
IsslopAnalysisOut,
|
|
IsslopListOut,
|
|
IsslopReportOut,
|
|
IsslopSourceOut,
|
|
)
|
|
from devplacepy.schemas.backups import (
|
|
BackupDashboardOut,
|
|
BackupJobOut,
|
|
BackupOut,
|
|
BackupScheduleOut,
|
|
BackupStoragePathOut,
|
|
)
|
|
from devplacepy.schemas.admin import (
|
|
AdminDeviiTaskItemOut,
|
|
AdminDeviiTasksOut,
|
|
AdminGameOut,
|
|
AdminMediaItemOut,
|
|
AdminMediaOut,
|
|
AdminNewsItemOut,
|
|
AdminNewsOut,
|
|
AdminNotificationsOut,
|
|
AdminSettingsOut,
|
|
AdminTrashOut,
|
|
AdminUserOut,
|
|
AdminUsersOut,
|
|
TrashItemOut,
|
|
)
|
|
from devplacepy.schemas.gateway import (
|
|
GatewayUsageOut,
|
|
UserAiUsageOut,
|
|
)
|
|
from devplacepy.schemas.uploads import (
|
|
UploadItemOut,
|
|
UploadsListOut,
|
|
)
|
|
from devplacepy.schemas.statistics import StatisticsOut
|
|
from devplacepy.schemas.auth import (
|
|
AuthPageOut,
|
|
DeviiPageOut,
|
|
LandingArticleOut,
|
|
LandingOut,
|
|
LandingPostOut,
|
|
)
|
|
from devplacepy.schemas.audit import (
|
|
AuditEntryOut,
|
|
AuditEventOut,
|
|
AuditLinkOut,
|
|
AuditLogOut,
|
|
)
|
|
from devplacepy.schemas.dbapi import (
|
|
DbColumnOut,
|
|
DbQueryOut,
|
|
DbRowOut,
|
|
DbRowsOut,
|
|
DbSchemaOut,
|
|
DbTableListOut,
|
|
DbTableOut,
|
|
NlQueryOut,
|
|
)
|
|
from devplacepy.schemas.battles import (
|
|
BattlesOut,
|
|
WarContributorOut,
|
|
WarEventOut,
|
|
WarEventsOut,
|
|
WarOut,
|
|
WarPersonOut,
|
|
WarViewerOut,
|
|
)
|
|
from devplacepy.schemas.quiz import (
|
|
QuizAnswerOut,
|
|
QuizAnswerResultOut,
|
|
QuizAttemptOut,
|
|
QuizAttemptPageOut,
|
|
QuizBuilderOut,
|
|
QuizDetailOut,
|
|
QuizDocumentOut,
|
|
QuizFormPageOut,
|
|
QuizLeaderboardEntryOut,
|
|
QuizLeaderboardOut,
|
|
QuizListItemOut,
|
|
QuizOptionOut,
|
|
QuizOut,
|
|
QuizProgressOut,
|
|
QuizQuestionOut,
|
|
QuizResultOut,
|
|
QuizScoreboardEntryOut,
|
|
QuizScoreboardOut,
|
|
QuizzesOut,
|
|
)
|
|
from devplacepy.schemas.game import (
|
|
GameCropOut,
|
|
GameFarmOut,
|
|
GameFarmViewOut,
|
|
GameLeaderboardEntryOut,
|
|
GameLeaderboardOut,
|
|
GameLegacyOut,
|
|
GamePerkOut,
|
|
GamePlotOut,
|
|
GameQuestOut,
|
|
GameStateOut,
|
|
)
|
|
from devplacepy.schemas.moderation import (
|
|
AcceptTermsOut,
|
|
AccountDeletionOut,
|
|
AdminModerationOut,
|
|
AdminReportOut,
|
|
ConsentListOut,
|
|
ConsentOut,
|
|
MaturityOut,
|
|
ModerationActionOut,
|
|
ReportCreatedOut,
|
|
ReportListOut,
|
|
ReportOut,
|
|
ReportReasonsOut,
|
|
SlaOut,
|
|
WorkspaceIndexItemOut,
|
|
WorkspaceIndexOut,
|
|
)
|